x86リアルモードでモニターを作ろうシリーズです。Windows11+wsl2で構築しています。
皆さんは開発していて整数が足りないと思ったことはないでしょうか?
ない?
そうですか。16Bitリアルモードで開発している変態誰かさんとは違いますか……
気を取り直して。
そう、16ビットのレジスタひとつで扱えるのは、たかだか 65,535 まで。32ビットに拡張したところで、42億。十分大きく見えるけれど、 現代の感覚からすればちょっとした統計計算や、少し精度の高い固定小数点演算で、あっという間にその壁にぶつかってしまいます。
これを何とかしたかった。選択肢として32bitライブラリを自作するってのがあるんですがテクニカルでめんどいうえ桁数も先に述べた通り十分じゃない。んじゃ、64Bitライブラリはといえば輪をかけてめんどいという。
小学生でもわかる簡単な方法で何とかならないものか?
あるんですね、これが。その方法とは『筆算』と呼ばれます。で、64Bitで表現できる10進20桁の筆算ライブラリを作ってみようと思うわけです。
今回、Hyfaxに導入することにしたのが BCD(二進化十進表現) ライブラリです。桁数さえ確保すれば「無限」に近い精度を扱えるBCDです。64Bitどころか128Bitだって怖くはないのです。処理時間さえ気にしなければ。
実装の裏側:PHSとDHS、I64HS
今回のライブラリでは、2つのデータ構造を使い分けています。
- PHS (Packed Handle Structure): メモリ効率を重視した形式。1バイトに2桁を詰め込みます。20桁の数値を10バイト程度で保持できる、いわば「保存用」の姿です。
- DHS (Digit Handle Structure): 計算効率を重視した形式。1バイトに1桁を贅沢に使い、内部的な演算(加減乗除)はこの形で行います。
- I64HS (Integer 64 Handle Structure): いわゆる64Bitバイナリです。外部とのやり取りはこの形式になります。
構造としてはこうですね。
;===============================
; Paced Handle Structure
;===============================
struc PHS
.val resb 20 ; packed BCD value (最大20桁)
.len resw 1 ; 有効桁数
.sign resb 1 ; 0=正, 1=負
endstruc
;===============================
; Digit Handle Structure
;===============================
struc DHS
.val resb 40 ; unpacked BCD value (最大40桁)
.len resw 1 ; 有効桁数
.sign resb 1 ; 0=正, 1=負
endstruc
;===============================
; Int 64 Handle Structure
;===============================
struc I64HS
.LowLow resw 1 ; bits 0-15
.LowHigh resw 1 ; bits 16-31
.HighLow resw 1 ; bits 32-47
.HighHigh resw 1 ; bits 48-63
endstruc
相互変換
まず最初に、I64HSを扱うユーティリティ、I64HSをPHSに変換する処理、PHSをDHSと相互変換する処理を作ります。
I64HS関連処理
I64HS構造体を0クリアする処理です。
; ----------------------------------------
; clear_i64hs
; 64ビット値を0クリアする
; 入力:
; DS:SI = I64HS構造体
; 出力:
; DS:SI = クリアした値
; 破壊:
; AX
; ----------------------------------------
clear_i64hs:
push ax
push di
xor ax, ax
mov [di + I64HS.LowHigh], ax
mov [di + I64HS.HighLow], ax
mov [di + I64HS.HighHigh], ax
pop di
pop ax
ret
I64HS構造体を2の補数表現にする処理です。
; ----------------------------------------
; i64_negate
; 64ビット値の2の補数を取る (符号反転)
; 入力:
; DS:DI = I64HS構造体
; 出力:
; DS:DI = 反転後の値
; 破壊:
; AX
; ----------------------------------------
i64_negate:
push bx
push cx
; ビット反転 (NOT)
mov bx, si
mov cx, 4
.not_loop:
mov ax, [bx]
not ax
mov [bx], ax
add bx, 2
loop .not_loop
; +1 (加算)
mov bx, si
mov ax, [bx + I64HS.LowLow]
add ax, 1
mov [bx + I64HS.LowLow], ax
mov ax, [bx + I64HS.LowHigh]
adc ax, 0
mov [bx + I64HS.LowHigh], ax
mov ax, [bx + I64HS.HighLow]
adc ax, 0
mov [bx + I64HS.HighLow], ax
mov ax, [bx + I64HS.HighHigh]
adc ax, 0
mov [bx + I64HS.HighHigh], ax
pop cx
pop bx
ret
I64HSからPHSに変換する処理です。
; -------------------------------------------------
; phs_from_i64
; 符号付き I64HS → PHS (pac BCD)
; 入力:
; DS:SI = I64HS
; ES:DI = PHS
; -------------------------------------------------
; PHS_VAL = packed BCD(絶対値)
; PHS_LEN = 桁数(10進)
; PHS_SIGN = 符号
phs_from_i64:
push ax
push bx
push cx
push dx
push si
push di
mov bp, di ; BP = PHS ptr を保持(DIが途中で壊れるので)
; -----------------------------
; 符号判定(入力は破壊しない:work_areaへコピーしてから)
; -----------------------------
; work_area = 入力コピー
push si
push di
mov di, work_area
mov cx, 4
.copy_in:
mov ax, [si]
mov [di], ax
add si, 2
add di, 2
loop .copy_in
pop di
pop si
; work_area の符号で判定
mov si, work_area
mov ax, [si + I64HS.HighHigh]
test ax, 0x8000
jz .positive
mov byte [es:bp + PHS_SIGN], 1
call i64_negate ; work_area を絶対値化
jmp .sign_done
.positive:
mov byte [es:bp + PHS_SIGN], 0
.sign_done:
; ---- zero normalization ----
mov ax, [si + I64HS.LowLow]
or ax, [si + I64HS.LowHigh]
or ax, [si + I64HS.HighLow]
or ax, [si + I64HS.HighHigh]
jnz .nz
; value == 0 → force +0
mov byte [es:bp + PHS_SIGN], 0
.nz:
; -----------------------------
; PHS_VAL クリア
; -----------------------------
lea bx, [ds:bp + PHS_VAL]
xor ax, ax
mov cx, 20
.clear_val:
mov [es:bx], al
inc bx
loop .clear_val
; -----------------------------
; double dabble(64回)
; DS:SI = work_area 固定
; ES:DI = BCD base
; -----------------------------
mov si, work_area
lea di, [ds:bp + PHS_VAL] ; 先頭10バイトだけ使う(20桁=10バイト)
mov cx, 64
.dd_loop:
push cx
mov cx, 10
call bcd_add3
pop cx
call i64_shl1
push cx
mov cx, 10
call bcd_shl1
pop cx
loop .dd_loop
; -----------------------------
; len 算出(MSB → LSB)
; -----------------------------
mov di, bp ; di = PHS base
mov dx, 19 ; 最大桁 index(0..19)
.len_scan:
mov bx, dx
shr bx, 1 ; byte index
mov al, [es:di + PHS_VAL + bx]
test dl, 1
jz .low
shr al, 4 ; 奇数桁 → high nibble
jmp .chk
.low:
and al, 0x0F ; 偶数桁 → low nibble
.chk:
cmp al, 0
jne .found
dec dx
jns .len_scan
; 全部 0 の場合 → PHS規約: len=0, sign=0
mov word [es:bp + PHS_LEN], 0
mov byte [es:bp + PHS_SIGN], 0
jmp .done
.found:
inc dx ; index → 桁数
mov [es:di + PHS_LEN], dx
; ---- PHS zero normalization (MUST be last) ----
mov ax, [es:di + PHS_LEN]
test ax, ax
jnz .nz_i64
; len == 0 → force +0
mov byte [es:di + PHS_SIGN], 0
.nz_i64:
.done:
push ds
mov ax, ds
mov ds, ax
mov si, bp
call phs_normalize
pop ds
; ---- PHS invariant: len==0 => sign=0 ----
mov ax, [es:di + PHS_LEN]
test ax, ax
jnz .ret_ok
mov byte [es:di + PHS_SIGN], 0
.ret_ok:
pop di
pop si
pop dx
pop cx
pop bx
pop ax
ret
PHS関連処理
PHSを正規化する処理です。
; ===============================
; phs_normalize
; DS:SI = PHS
; PHS_LEN を有効桁数に正規化
; ゼロなら sign=0, len=0 にする
; ===============================
phs_normalize:
push ax
push bx
push cx
push dx
push di
; cx = len
mov cx, [si + PHS_LEN]
test cx, cx
jnz .check_top
; len == 0 → 0(PHS規約)
mov byte [si + PHS_SIGN], 0
jmp .done
.check_top:
; di = val base
lea di, [si + PHS_VAL]
.normalize_loop:
cmp cx, 1
jbe .check_zero ; len==1 まで来たら終了候補
; top digit index = cx-1
mov dx, cx
dec dx
; byte index = dx / 2
mov bx, dx
shr bx, 1
mov al, [di + bx]
; nibble 選択
test dl, 1
jz .low_nib
shr al, 4
jmp .chk
.low_nib:
and al, 0x0F
.chk:
test al, al
jne .done_len ; 非0ならここで終了
; 上位 0 桁を捨てる
dec cx
jmp .normalize_loop
.done_len:
mov [si + PHS_LEN], cx
jmp .done
.check_zero:
; len==1 だが digit==0 なら len=0 に
mov al, [di]
and al, 0x0F
test al, al
jnz .done_len
mov word [si + PHS_LEN], 0
mov byte [si + PHS_SIGN], 0
.done:
pop di
pop dx
pop cx
pop bx
pop ax
ret
PHSをDHSに変換する処理です。
;--------------------------------------------------
; phs_unpack
; SI = PHS*
; DI = DHS*
; destroys: AX,BX,CX,DX
;--------------------------------------------------
phs_unpack:
; ---- copy len / sign ----
mov ax, [si + PHS_LEN]
mov [di + DHS_LEN], ax
mov al, [si + PHS_SIGN]
mov [di + DHS_SIGN], al
; ---- clear DHS_VAL (40 bytes) ----
push si
push di
lea bx, [di + DHS_VAL]
mov cx, 40
xor ax, ax
.clr:
mov [bx], al
inc bx
loop .clr
pop di
pop si
; ---- unpack digits ----
; BX = digit index (0..len-1)
xor bx, bx
mov cx, [si + PHS_LEN]
.loop:
cmp bx, cx
jae .done
; byte index = bx >> 1
mov dx, bx
shr dx, 1
; read packed byte
push bx
mov bx, dx
mov al, [si + PHS_VAL + bx]
pop bx
; even digit -> low nibble, odd -> high nibble
test bl, 1
jz .low
; odd: high nibble
shr al, 4
jmp .store
.low:
; even: low nibble
and al, 0x0F
.store:
mov [di + DHS_VAL + bx], al
inc bx
jmp .loop
.done:
ret
DHSをPHSに変換する処理です。
;--------------------------------------------------
; phs_pack
; SI = DHS*
; DI = PHS*
; destroys: AX,BX,CX,DX
;--------------------------------------------------
phs_pack:
; ---- copy len / sign ----
mov ax, [si + DHS_LEN]
mov [di + PHS_LEN], ax
mov al, [si + DHS_SIGN]
mov [di + PHS_SIGN], al
; ---- clear PHS_VAL (20 bytes) ----
push si
push di
lea bx, [di + PHS_VAL]
mov cx, 20
xor ax, ax
.clr:
mov [bx], al
inc bx
loop .clr
pop di
pop si
; ---- pack digits ----
; BX = digit index (0..len-1)
xor bx, bx
mov cx, [si + DHS_LEN]
.loop:
cmp bx, cx
jae .done
; get digit (0..9)
mov al, [si + DHS_VAL + bx]
and al, 0x0F
; byte index = bx >> 1
mov dx, bx
shr dx, 1
; read current packed byte
push bx
mov bx, dx
mov ah, [di + PHS_VAL + bx]
pop bx
; even digit -> low nibble, odd -> high nibble
test bl, 1
jz .low
; odd: high nibble
shl al, 4
and ah, 0x0F ; keep low nibble
or ah, al
push bx
mov bx, dx
mov [di + PHS_VAL + bx], ah
pop bx
jmp .next
.low:
; even: low nibble
and ah, 0xF0 ; keep high nibble
or ah, al
push bx
mov bx, dx
mov [di + PHS_VAL + bx], ah
pop bx
.next:
inc bx
jmp .loop
.done:
; normalize to ensure zero has sign=0
push si
mov si, di
call phs_normalize
pop si
ret
はい。おなか一杯になりました。
導入だけになってしまいましたが、今回はここまでにします。
次回以降、どうなるんでしょう。不安です。
《2026/1/12 01:41:24》