乗算/除算/ビットシフト

乗算について
> 汎用命令 (2)

MUL 命令 (符号なし) または IMUL 命令 (符号付き) の乗算では、同じサイズのオペランドの値を掛けて、その2倍のサイズの結果を、レジスタに格納します。

(8bit * 8bit = 16bit の場合)

MUL  : 4 * 255(0xFF) = 1020(0x3FC) ; CF OF
IMUL : 4 * -1(0xFF) = -4(0xFFFC)   ; None

MUL  : 252(0xFC) * 248(0xF8) = 62496(0xF420) ; CF OF
IMUL : -4(0xFC) * -8(0xF8) = 32  ; None

IMUL : 64 * 2 = 128(0x80) ; CF OF
IMUL : 64 * -2 = -128     ; None

乗算の場合は、符号付きと符号なしのどちらで扱うかによって、結果の値が異なります。
そのため、符号付きと符号なしで、それぞれの乗算命令が存在します。

乗算では、負と負の値を掛けた場合、結果は正になります。
ステータスフラグ
MUL/IMUL 命令では、CF と OF フラグは、それぞれ同じ値にセットされます。
桁あふれ、またはオーバーフローが発生した場合、1 になります。

※他のフラグは未定義となっているので、演算後にフラグが変化していたとしても、無視してください。

MUL (符号なし) の場合、結果が、元のサイズから桁あふれした場合 (結果の上位ビットが 0 でない場合)、1 になります。
IMUL (符号付き) の場合、結果が、元のサイズで表現できる符号付きの範囲を超えた場合に、1 になります。

IMUL (8bit) の 64 * 2 = 128 は、結果の 16bit サイズにおいては範囲内の値ですが、符号付き 8bit では表現できない値 (-128〜127 の範囲外) となるので、オーバーフローになります。

IMUL (8bit) の 64 * -2 = -128 は、符号付き 8bit で表現できる値なので、CF, OF は 0 になります。
除算
除算では、ステータスフラグは変更されません。
代わりに、例外が発生します。

値 0 で割ろうとした場合、ゼロ除算例外が発生して、プログラムは強制終了します。

; -128(AX) / 5(BL)
mov ax, -128
mov bl, 5
idiv bl
; AL (商): -25
; AH (剰余): -3

--------
; -128 / -5
mov ax, -128
mov bl, -5
idiv bl
; AL (商): 25
; AH (剰余): -3

-128 ÷ 5 = -25.6 ですが、小数点以下は切り捨てられるので、商は -25 です。
余りは、128 - 25 * 5 = 3。これに被除数の符号を付けるので、剰余は -3 です。

負÷負の場合、商は正になりますが、剰余は常に被除数の符号と同じです。
ビットシフト
SAR 命令は、符号付きの右シフトです。
最上位ビットには、元の符号ビットがコピーされます。

符号付きの値を 2,4,8,16... で割るのと同じ結果になりますが、負の値を符号付きで右シフトする場合、除算命令と右シフトでは、結果の値が異なる場合があるので、注意してください。

[-11 >> 2] 符号付き右シフト

mov al, -11 ; 0xF5
sar al, 2
; AL = -3 (0xFD)
; 1111_0101 -> 1111_1101

---------
[-11 / 4] 符号付き除算

mov ax, -11
mov bl, 4
idiv bl
; AL = -2 (商)

SAR と IDIV 命令では、どちらも4で割るという意味では同じですが、結果の商の値は、SAR が -3、IDIV が -2 になっています。

-11 / 4 = -2.75 です。
小数点以下を切り捨てた場合は -2 ですが、切り上げると -3 になります。

負の値を右シフトした場合、負の無限大に近い方に切り上げられてしまう場合があるため、注意が必要です。
(正の値の場合は、どちらでも結果は変わりません)
値の調整
右シフトが、除算の結果と同じになるようにしたい場合は、切り上げにならないように、元の値を調整しておく必要があります。

0 に近い方に調整する必要があるため、値を足すことになります。
右シフトの数が N の場合、元の値に ((1 << N) - 1) を足してから右シフトすると、除算と同じ結果になります。

N = 2 の場合、(1<<2) - 1 = 3 を足す

 -7 + 3 : 1111_1100b >> 2 = 1111_1111b (-1) ; -7 / 4 = -1

 -8 + 3 : 1111_1011b >> 2 = 1111_1110b (-2) ; -8 / 4 = -2
 -9 + 3 : 1111_1010b >> 2 = 1111_1110b (-2)
-10 + 3 : 1111_1001b >> 2 = 1111_1110b (-2)
-11 + 3 : 1111_1000b >> 2 = 1111_1110b (-2)

-12 + 3 : 1111_0111b >> 2 = 1111_1101b (-3) ; -12 / 4 = -3

ただし、元の値が負の値の場合のみ、加算する必要があります。
正の値の時に加算してしまうと、切り上げになってしまいます。((11 + 3) >> 2 = 3)