X86アセンブリにはモジュロ演算子または命令のようなものがありますか?
モジュラス/除数が既知の定数であり、パフォーマンスに関心がある場合は、 this および this を参照してください。乗数の逆数は、実行時までわからないループ不変値に対しても可能です。 https://libdivide.com/ を参照してください(ただし、JIT code-genがないと、1つの定数に必要なステップだけをハードコーディングするよりも効率が低下します)。
既知の2のべき乗に対してdiv
を使用しないでください。muchは、and
よりも遅い、または右除算のためのシフト。 2のべき乗による符号なしまたは符号付き除算の例については、Cコンパイラの出力をご覧ください。 Godboltコンパイラエクスプローラー 。ランタイム入力が2のべき乗であることがわかっている場合は、_lea eax, [esi-1]
_を使用します。 _and eax, edi
_またはx & (y-1)
を実行するようなもの。モジュロ256はさらに効率的です。2つのレジスタが分離されている限り、_movzx eax, cl
_は最近のIntel CPUでゼロレイテンシを持ちます(mov-elimination)。
DIV
命令(およびそれに対応する IDIV
符号付き数値)は商と剰余の両方を与えます。符号なしの場合、剰余とモジュラスは同じものです。符号付きidiv
の場合、 剰余(モジュラスではない) になります。
例えば。 _-5 / 2 = -2 rem -1
_。 x86除算のセマンティクスは、C99の_%
_演算子と完全に一致します。
_DIV r32
_は、_EDX:EAX
_の64ビット数を(レジスタまたはメモリ内の)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 quotient
; EAX = 123 = 1234 / 10 remainder
_
16ビットアセンブリでは、_div bx
_を実行して、EBX
で_EDX:EAX
_の32ビットオペランドを除算できます。詳細については、Intel Architectures Software Developer's Manuals を参照してください。
通常、常に符号なしdiv
の前に_xor edx,edx
_を使用して、EAXをEDX:EAXにゼロ拡張します。これは、「通常の」32ビット/ 32ビット=> 32ビット除算を行う方法です。
署名された部門の場合、cdq
をidiv
の前に使用して、からsign-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ビットのoperand-sizeは特別であることに注意してください。暗黙の入出力は、DL:ALではなく、AH:AL(別名AX)にあります。例については、「 8086 Assembly on DOSBox:Bug with idiv instruction? 」を参照してください。
署名付き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ビット=> 32ビット除算または剰余による除算のための新しいオペコードはありません。
除算は非常に遅く、(できれば)まれであるため、EAXとEDXを回避する方法を追加したり、直接イミディエイトを使用したりすることはありません。
divとidivは、商が1つのレジスタに収まらない場合にエラーになります(AL/AX/EAX/RAX、被除数と同じ幅)。これにはゼロによる除算が含まれますが、ゼロ以外のEDXおよびより小さい除数でも発生します。これが、Cコンパイラが32ビット値をDX:AXに分割する代わりに、ゼロ拡張または符号拡張するだけの理由です。
また、_INT_MIN / -1
_がCの未定義の動作である理由:x86のような2の補数システムの符号付き商がオーバーフローします。 x-86とARMの例については、 -1(負の整数)による整数除算の結果がFPEになる理由 を参照してください。この場合、x86 idiv
は実際にフォールトします。
X86例外は_#DE
_-除算例外です。 Unix/Linuxシステムでは、カーネルは#DE例外を引き起こすプロセスにSIGFPE算術例外信号を送信します。 ( どのプラットフォームで整数によるゼロ除算が浮動小数点例外をトリガーしますか? )
div
の場合、_high_half < divisor
_で配当を使用しても安全です。例えば_0x11:23 / 0x12
_は_0xff
_より小さいため、8ビットの商に収まります。
1つのチャンクの剰余を次のチャンクの上位半分の被除数(EDX)として使用することで、巨大な数値を小さな数値で拡張精度で除算することができます。これがおそらく、彼らが他の方法ではなく残り= EDX quotient = EAXを選んだ理由です。
2のべき乗を法として計算する場合、ビット単位のANDを使用する方が、除算を実行するよりも簡単で一般的に高速です。 b
が2の累乗の場合、a % b == a & (b - 1)
。
たとえば、レジスタEAX、モジュロ64の値を取得してみましょう。
最も簡単な方法はAND EAX, 63
、63はバイナリで111111であるため。
マスクされた上位の数字は、私たちにとって関心のないものです。やってみて!
同様に、2のべき乗でMULまたはDIVを使用する代わりに、ビットシフトを使用する方法があります。ただし、符号付き整数に注意してください!