x86アセンブリにモジュロ演算子または命令のようなものはありますか?
4 に答える
係数/除数が既知の定数であり、パフォーマンスに関心がある場合は、thisおよび thisを参照してください。実行時までわからないループ不変の値に対しては、乗法逆も可能です 。絶え間ない。)
div
既知の 2 の累乗には使用しないand
でください。剰余や除算の右シフトよりもはるかに低速です。Godbolt コンパイラーの explorerなどで、2 の累乗による符号なしまたは符号付き除算の例については、C コンパイラーの出力を参照してください。ランタイム入力が 2 のべき乗であることがわかっている場合は、lea eax, [esi-1]
;を使用します。and eax, edi
またはそのようなことをするx & (y-1)
。モジュロ 256 はさらに効率的です。2 つのレジスタが分離されている限り、movzx eax, cl
最近の Intel CPU ( mov-elimination ) ではレイテンシがゼロです。
単純/一般的なケース: 実行時の不明な値
DIV
命令(および符号付き数値の対応するIDIV
命令) は、商と剰余の両方を提供します。符号なしの場合、剰余とモジュラスは同じものです。signedidiv
の場合、負になる可能性のある剰余 (モジュラスではない)
が得られます-5 / 2 = -2 rem -1
。x86 除算のセマンティクスは、C99 の%
演算子と正確に一致します。
DIV r32
64 ビット数値をEDX:EAX
32 ビット オペランド (任意のレジスタまたはメモリ内) で除算し、商をEAX
に、剰余を に格納しEDX
ます。商のオーバーフローでエラーになります。
符号なし 32 ビットの例(どのモードでも動作)
mov eax, 1234 ; dividend low half
mov edx, 0 ; dividend high half = 0. prefer xor edx,edx
mov ebx, 10 ; divisor can be any register or memory
div ebx ; Divides 1234 by 10.
; EDX = 4 = 1234 % 10 remainder
; EAX = 123 = 1234 / 10 quotient
div bx
16 ビット アセンブリでは、32 ビット オペランドを で割るDX:AX
ことができますBX
。詳細については、Intel のArchitectures Software Developer's Manualsを参照してください。
通常は常にxor edx,edx
before unsigneddiv
を使用して、EAX を EDX:EAX にゼロ拡張します。 これは、「通常の」32ビット/ 32ビット=> 32ビット除算を行う方法です。
符号付き除算の場合、beforeを使用してcdq
idiv
符号を付けます - EAX を EDX:EAX に拡張します。DIV 命令を使用する前に EDX を 0 にする必要があるのはなぜですか?も参照してください。. 他のオペランド サイズについては、cbw
(AL->AX)、cwd
(AX->DX:AX)、cdq
(EAX->EDX:EAX)、またはcqo
(RAX->RDX:RAX) を使用して、上半分を設定し0
ます。-1
下位半分の符号ビット。
div
/idiv
は、8、16、32、および (64 ビット モードでは) 64 ビットのオペランド サイズで使用できます。64 ビットのオペランド サイズは、現在の Intel CPU では 32 ビット以下よりもはるかに低速ですが、AMD CPU はオペランド サイズに関係なく、数値の実際の大きさのみを考慮します。
8 ビットのオペランド サイズは特殊であることに注意してください。暗黙的な入力/出力は、DL:AL ではなく、AH:AL (別名 AX) にあります。DOSBox の 8086 アセンブリを参照してください: idiv 命令のバグ? たとえば。
符号付き 64 ビット除算の例(64 ビット モードが必要)
mov rax, 0x8000000000000000 ; INT64_MIN = -9223372036854775808
mov ecx, 10 ; implicit zero-extension is fine for positive numbers
cqo ; sign-extend into RDX, in this case = -1 = 0xFF...FF
idiv rcx
; quotient = RAX = -922337203685477580 = 0xf333333333333334
; remainder = RDX = -8 = 0xfffffffffffffff8
制限事項 / よくある間違い
div dword 10
マシンコードにエンコードできません(そのため、アセンブラーは無効なオペランドに関するエラーを報告します)。
mul
/とは異なりimul
(上位半分の結果を書き込む時間を無駄にしないために、通常は高速な 2 オペランドimul r32, r/m32
または 3オペランドを使用する必要がありますimul r32, r/m32, imm8/32
)、即値または 32 ビット/32- による除算用の新しいオペコードはありません。 bit => 32 ビットの除算または剰余 (上位半割被除数入力なし)。
除算は非常に遅く、(できれば) まれであるため、EAX と EDX を回避したり、即時型を直接使用したりする方法をわざわざ追加しませんでした。
div と idiv は、商が 1 つのレジスタ(AL / AX / EAX / RAX、被除数と同じ幅) に収まらない場合にエラーになります。これにはゼロによる除算が含まれますが、0 以外の EDX とより小さい除数でも発生します。これが、C コンパイラが 32 ビット値を DX:AX に分割するのではなく、単にゼロ拡張または符号拡張する理由です。
また、INT_MIN / -1
C の動作が未定義なのはなぜですか。x86 のような 2 の補数システムでは、符号付き商がオーバーフローします。-1 (負の 1) による整数除算が FPE になるのはなぜですか?を参照してください。x86 と ARM の例。この場合、 x86idiv
は確かに失敗します。
x86 の例外は#DE
- 除算例外です。Unix/Linux システムでは、カーネルは #DE 例外を引き起こすプロセスに SIGFPE 算術例外シグナルを送信します。(整数をゼロで割ると浮動小数点例外が発生するのはどのプラットフォームですか? )
の場合div
、 での被除数の使用high_half < divisor
は安全です。たとえば0x11:23 / 0x12
、より小さい0xff
ので、8 ビットの商に収まります。
1 つのチャンクの剰余を次のチャンクの上位半割被除数 (EDX) として使用することにより、巨大な数を小さい数で割る拡張精度の除算を実装できます。これがおそらく、その逆ではなく、剰余=EDX 商=EAX を選択した理由です。
2 の累乗を法として計算する場合、ビットごとの AND を使用する方が除算を実行するよりも簡単で、一般的に高速です。b
が 2 の累乗の場合、 a % b == a & (b - 1)
.
たとえば、レジスタ EAX のモジュロ 64の値を考えてみましょう。63 は 2 進数で 111111 であるため、
最も簡単な方法はです。AND EAX, 63
マスクされた上位の数字は重要ではありません。やってみて!
同様に、2 のべき乗で MUL または DIV を使用する代わりに、ビットシフトが適しています。ただし、符号付き整数に注意してください!