1. ホーム
  2. assembly

[解決済み] アセンブリ言語 - Moduloはどのように行うのですか?

2022-02-02 13:32:44

質問

x86アセンブリでモジュロ演算子や命令のようなものはありますか?

どのように解決するのですか?

モジュラス/ディバイダーが既知の定数であり、パフォーマンスを重視する場合は、以下を参照してください。 これ これ . 実行時までわからないループ不変の値に対しては、乗法的な逆数も可能です。 https://libdivide.com/ (ただし、JITコードジェンなしだと、1つの定数に必要なステップだけをハードコーディングするよりも効率が悪い)。

は絶対に使用しないでください。 div は2の既知の累乗です。 大いに よりも遅い and は余り、右シフトは割り算です。 Cコンパイラの出力を見て、2の累乗による符号なしまたは符号ありの除算の例を見てください。 Godbolt コンパイラエクスプローラで . 実行時の入力が 2 のべき乗であることが分かっている場合は、次のようにします。 lea eax, [esi-1] ; and eax, edi というようなことをするために x & (y-1) . Modulo 256はさらに効率的です。 movzx eax, cl は、最近のIntel CPUではレイテンシがゼロになる( mov-elimination(ムーブエリミネーション )、2つのレジスタが別々である限りは


単純/一般的な場合:実行時に値が不明な場合

DIV インストラクション (およびその対応する IDIV は商と余りを表します。 符号なしの場合、余りとモジュールは同じものです。 符号ありの場合 idiv は、次のようになります。 余り(モジュラスではない) は負になることがあります。
-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

16ビットアセンブリでは、次のようになります。 div bx で32ビットオペランドを分割する場合 DX:AXBX . インテルの アーキテクチャ ソフトウェア開発者向けマニュアル をご覧ください。

通常は常に xor edx,edx の前に符号なし div でEAXをEDX:EAXにゼロ拡張しています。 これが、"normal" 32-bit / 32-bit => 32-bit divisionのやり方です。

符号付き除算の場合。 使用 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ビット => 32ビット除算や余りのための新しいオペコードはありません。

除算はとても遅く、(願わくば)まれなことなので、EAX や EDX を回避させる方法や、即値を直接使用する方法をわざわざ追加する必要はなかったのです。


div と idiv は商が1つのレジスタに収まらない場合、エラーになります。 (AL / AX / EAX / RAX、配当と同じ幅)。 これはゼロによる除算を含みますが、ゼロでない EDX とより小さい除数でも起こります。 Cコンパイラが32ビット値をDX:AXに分割する代わりに、ゼロ拡張や符号拡張を行うのはこのためです。

また、なぜ INT_MIN / -1 x86 のような 2 の補数系では符号付き商をオーバーフローさせるからです。 参照 なぜ整数の-1(マイナス1)除算はFPEになるのですか? x86とARMの比較の例です。 idiv は、この場合、確かに不具合が発生します。

x86の例外は #DE - 除算例外です。 Unix/Linux システムでは、カーネルは #DE 例外を発生させたプロセスに SIGFPE 算術例外シグナルを送出します。 ( ゼロによる整数の除算が浮動小数点例外を引き起こすのはどのプラットフォームですか? )

について div を使用すると、配当が high_half < divisor は安全です。 0x11:23 / 0x12 よりも小さい。 0xff ということで、8ビットの商に収まる。

巨大な数を小さな数で割る拡張精度の除算は、ある塊の余りを次の塊の上位半数配当(EDX)として使用することで実装できます。 このため、remainder=EDX quotient=EAXとしたのでしょう。