乗算について
> 汎用命令 (2)
MUL 命令 (符号なし) または IMUL 命令 (符号付き) の乗算では、同じサイズのオペランドの値を掛けて、その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 になります。
桁あふれ、またはオーバーフローが発生した場合、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 ÷ 5 = -25.6 ですが、小数点以下は切り捨てられるので、商は -25 です。
余りは、128 - 25 * 5 = 3。これに被除数の符号を付けるので、剰余は -3 です。
負÷負の場合、商は正になりますが、剰余は常に被除数の符号と同じです。
代わりに、例外が発生します。
値 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... で割るのと同じ結果になりますが、負の値を符号付きで右シフトする場合、除算命令と右シフトでは、結果の値が異なる場合があるので、注意してください。
SAR と IDIV 命令では、どちらも4で割るという意味では同じですが、結果の商の値は、SAR が -3、IDIV が -2 になっています。
-11 / 4 = -2.75 です。
小数点以下を切り捨てた場合は -2 ですが、切り上げると -3 になります。
負の値を右シフトした場合、負の無限大に近い方に切り上げられてしまう場合があるため、注意が必要です。
(正の値の場合は、どちらでも結果は変わりません)
最上位ビットには、元の符号ビットがコピーされます。
符号付きの値を 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) を足してから右シフトすると、除算と同じ結果になります。
ただし、元の値が負の値の場合のみ、加算する必要があります。
正の値の時に加算してしまうと、切り上げになってしまいます。((11 + 3) >> 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)