割り算の商と余りを同時に計算する(x86, x64, C言語)
「割り算の商と余りを同時に計算する」と書きましたが、特別な方法を使うわけではありません。x86/x64 の割り算命令は商と余り(剰余)の両方を同時に計算してくれます。
動作確認環境
- Windows 11 Home 24H2
- Visual Studio Community 2022 (Visual C++)
x86/x64 の割り算命令
x86/x64 アセンブリ言語の割り算命令は div(符号なし)または idiv(符号付き)です。
Intel のドキュメントから div と idiv の表を引用します。

赤枠で囲った行は次の意味になります。
Quadword/doubleword、言い換えると QWORD(64bit) / DWORD(32bit) の割り算について :
・ 割られる数 = edx と eax レジスタの組み合わせで指定 (64bit)
・ 割る数 = レジスタやメモリアドレスで指定 (32bit)
↓ idiv 命令
・ 商 = eax レジスタに格納される (32bit)
・ 余り = edx レジスタに格納される (32bit)
00000001:234500EF(h) / 00001000(h) の符号付き割り算を行う場合、割られる数 00000001:234500EF(h) を edx:eax レジスタにセットし、割る数 00001000(h) をたとえば ecx レジスタにセットし、idiv 命令を実行します。
mov edx, 00000001h ; 割られる数(上位)
mov eax, 234500EFh ; 割られる数(下位)
mov ecx, 00001000h ; 割る数
idiv ecx ; 割り算
すると、商 00123450(h) が eax レジスタに 、余り 000000EF(h) が edx レジスタに格納されます。
割り算を行い商と余りを表示する、x86 アセンブリ言語プログラムを書いてみます。画面表示には C ランタイムライブラリの printf 関数を利用します。
.686
.model flat
EXTERN _printf: PROC
.data
PRINTFORMAT BYTE "q=%08X(h), r=%08X(h)", 0Dh, 0Ah, 0
.code
_main PROC
; push ebp
; mov ebp, esp
;------ 割り算
mov edx, 00000001h ; 割られる数(上位)
mov eax, 234500EFh ; 割られる数(下位)
mov ecx, 00001000h ; 割る数
idiv ecx ; 割り算
; ここで、商 00123450(h) は eax レジスタに 、
; 余り 000000EF(h) は edx レジスタに格納される。
;------ 結果の表示
push edx ; 第 3 引数(余り)
push eax ; 第 2 引数(商)
push OFFSET PRINTFORMAT ; 第 1 引数(フォーマット)
call _printf ; printf 関数呼び出し
add esp, 12 ; スタックのクリーンアップ
; mov esp, ebp
; pop ebp
ret
_main ENDP
END
x86 Native Tools Command Prompt for VS 2022 のコマンドプロンプトを開き、

MASM(マイクロソフトマクロアセンブラー)の ml コマンドでアセンブルします。
C:\tmp> ml test1.asm /link msvcrt.lib ucrt.lib legacy_stdio_definitions.lib /subsystem:console
Microsoft (R) Macro Assembler Version 14.39.33523.0
Copyright (C) Microsoft Corporation. All rights reserved.
Assembling: test1.asm
Microsoft (R) Incremental Linker Version 14.39.33523.0
Copyright (C) Microsoft Corporation. All rights reserved.
/OUT:test1.exe
test1.obj
msvcrt.lib
ucrt.lib
legacy_stdio_definitions.lib
/subsystem:console
生成された test1.exe ファイルを実行します。
C:\tmp> test1.exe
q=00123450(h), r=000000EF(h)
予想通り、idiv 命令により商と余りが同時に計算され、結果が表示されました。
商が範囲内に収まらないと例外発生
少し話が変わりますが、いま計算した
- 00000001:234500EF(h) / 00001000(h) = 00123450(h) 余り 000000EF(h)
は、
- 割られる数 64bit / 割る数 32bit = 商 32bit 余り 32bit
のビット長に収まっています。しかし、割る数を 1 にすると
- 00000001:234500EF(h) / 00000001(h) = 00000001:234500EF(h) 余り 00000000(h)
となり、商が eax レジスタの 32bit を超えてしまいます。この計算をするとどうなるのでしょうか。
さきほどのプログラムを書き換え、割る数を 1 にします。
......
;------ 割り算
mov edx, 00000001h ; 割られる数(上位)
mov eax, 234500EFh ; 割られる数(下位)
mov ecx, 00000001h ; 割る数 ★ 1 に変更
idiv ecx ; 割り算
......
アセンブルします。
C:\tmp> ml test2.asm /link msvcrt.lib ucrt.lib legacy_stdio_definitions.lib /subsystem:console
実行します。
C:\tmp> test2.exe
手元の PC は例外が発生すると WinDbg が起動するよう設定してあり(WinDbg -I)、test2.exe を実行した瞬間に WinDbg が起動しました。

WinDbg の画面は「idiv eax, ecx」命令で「Integer overflow – code c0000095」の例外が発生したことを示しています。idiv 命令は、ゼロ除算のときだけでなく、商が既定の範囲を超えた場合も例外が発生するようです。
改めて Intel のドキュメントを見ると、idiv 命令の動作の説明にその旨書いてありました(#DE, Divide error)。
......
IF OperandSize = 32 (* Quadword/doubleword operation *)
temp := EDX:EAX / SRC; (* Signed division *)
IF (temp > 7FFFFFFFH) or (temp < 80000000H)
(* If a positive result is greater than 7FFFFFFFH
or a negative result is less than 80000000H *)
THEN
#DE; (* Divide error *)
ELSE
EAX := temp;
EDX := EDXE:AX SignedModulus SRC;
FI;
FI;
......
C 言語の「/」と「%」をひとつの idiv 命令にできる?
C 言語では、「/」が商を求める、「%」が余りを求める演算子です。「/」と「%」を連続して使うと、コンパイラによってひとつの idiv 命令にまとめられるのでしょうか。試してみます。
コマンドライン引数として割られる数と割る数を受け取り、商と余りを計算・表示する C 言語のプログラム「test3.c」を書きます。エラーチェックは省略しています。
#include <stdio.h>
int main(int argc, char *argv[])
{
int divd, divr, q, r;
divd = atoi(argv[1]); // 割られる数
divr = atoi(argv[2]); // 割る数
q = divd / divr; // 商
r = divd % divr; // 余り
printf("q=%d, r=%d\n", q, r);
return 0;
}
x86 Native Tools Command Prompt for VS 2022 のコマンドプロンプトを開き、cl コマンドでビルドします。「/Od」は最適化なし、「/FAs」はアセンブリリストを生成、の意味です。
C:\tmp> cl /Od /FAs test3.c
生成されたアセンブリリストを見てみます。
......
; 10 : q = divd / divr; // 商
mov eax, DWORD PTR _divd$[ebp]
cdq
idiv DWORD PTR _divr$[ebp]
mov DWORD PTR _q$[ebp], eax
; 11 : r = divd % divr; // 余り
mov eax, DWORD PTR _divd$[ebp]
cdq
idiv DWORD PTR _divr$[ebp]
mov DWORD PTR _r$[ebp], edx
......
「/」に 1 回、「%」にもう 1 回、合わせて 2 回、idiv 命令が呼び出されています。最適化なしでビルドしたので当然でしょう。
次に速度最適化「/O2」のオプションを付けてビルドしてみます。
C:\tmp> cl /O2 /FAs test3.c
生成されたアセンブリリストを見てみます。
......
; 10 : q = divd / divr; // 商
; 11 : r = divd % divr; // 余り
mov eax, edi
cdq
idiv ecx
......
「/」と「%」の 2 つの演算が、ひとつの idiv 命令にまとめられました。期待通りです。
ところが、プログラムを少し変えると結果が違ってきます。以下は、割られる数と割る数の代入に atoi 関数ではなく scanf 関数を使った「test4.c」です。
#include <stdio.h>
int main(int argc, char *argv[])
{
int divd, divr, q, r;
scanf("%d", &divd); // 割られる数
scanf("%d", &divr); // 割る数
q = divd / divr; // 商
r = divd % divr; // 余り
printf("q=%d, r=%d\n", q, r);
return 0;
}
速度最適化「/O2」のオプションを付けてビルドします。
C:\tmp> cl /O2 /FAs test4.c
生成されたアセンブリリストを見てみます。
......
; 10 : q = divd / divr; // 商
; 11 : r = divd % divr; // 余り
mov eax, DWORD PTR _divd$[esp+24]
cdq
idiv DWORD PTR _divr$[esp+24]
mov eax, DWORD PTR _divd$[esp+24]
; 12 :
; 13 : printf("q=%d, r=%d\n", q, r);
push edx
cdq
idiv DWORD PTR _divr$[esp+28]
push eax
push OFFSET ??_C@_0M@CMCCKACO@q?$DN?$CFd?0?5r?$DN?$CFd?6@
call _printf
......
最適化がうまくいっていないのか、idiv 命令が無駄に 2 回呼び出されています。
連続する「/」と「%」がひとつの idiv 命令にまとめられるかどうかは文脈によりけりで生成されたコードを見てみないとわからない、と言えそうです。
C 言語の div 関数はひとつの idiv 命令になる?
C 言語のライブラリには商と余りを同時に求める div 関数があります。
書式は次の通りです。引数に割られる数と割る数を指定します。
#include <stdlib.h>
div_t div(
int numer,
int denom
);
計算結果は div_t 型の構造体で返ってきます。
typedef struct _div_t
{
int quot;
int rem;
} div_t;
この div 関数の実体はアセンブリ言語の div/idiv 命令なのでしょうか。
先の「test3.c」をベースに、「/」や「%」演算子の代わりに div 関数を使った「test5.c」を書いてみます。
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int divd, divr;
div_t ans;
divd = atoi(argv[1]); // 割られる数
divr = atoi(argv[2]); // 割る数
ans = div(divd, divr); // 割り算をして商と余りを構造体で返却
printf("q=%d, r=%d\n", ans.quot, ans.rem);
return 0;
}
最適化なしでビルドします。「/Zi」は「デバッグ情報を生成」の意味です(.exe ファイルの逆アセンブルを見やすくするために付加)。
c:\tmp> cl /Od /FAs /Zi test5.c
生成されたアセンブリリストと .exe の逆アセンブル結果 (dumpbin /disasm test5.exe) から div 関数の処理を抜き出したものが以下です。
......
; 12 : ans = div(divd, divr); // 割り算をして商と余りを構造体で返却
mov eax, DWORD PTR _divr$[ebp]
push eax
mov ecx, DWORD PTR _divd$[ebp]
push ecx
call _div
add esp, 8
mov DWORD PTR _ans$[ebp], eax
mov DWORD PTR _ans$[ebp+4], edx
......
_div:
mov edi,edi
push ebp
mov ebp,esp
mov eax,dword ptr [ebp+8]
cdq
idiv eax,dword ptr [ebp+0Ch]
pop ebp
ret
......
期待通り、div 関数の実体は、idiv 命令 1 回で商と余りの計算を済ませていました。ただし、関数呼び出しのオーバーヘッド(call _div)が気になります。
速度最適化を指定してビルドするとどうなるでしょうか。
c:\tmp> cl /O2 /FAs test5.c
アセンブリリストの抜粋です。
......
; 12 : ans = div(divd, divr); // 割り算をして商と余りを構造体で返却
mov eax, edi
cdq
idiv ecx
......
div 関数の呼び出しがインライン展開され、その場で idiv 命令が発行されるようになりました。
しかし改めて見てみると、「/」と「%」を使った「test3.c」も、div 関数を使った「test5.c」も、速度最適化 ON で生成されるコードは同じであり、あまり馴染みのない div 関数を使うメリットが感じられません。
今度は「test4.c」をベースに、「/」や「%」演算子の代わりに div 関数を使った「test6.c」を書いてみます。
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int divd, divr;
div_t ans;
scanf("%d", &divd); // 割られる数
scanf("%d", &divr); // 割る数
ans = div(divd, divr); // 割り算をして商と余りを構造体で返却
printf("q=%d, r=%d\n", ans.quot, ans.rem);
return 0;
}
速度最適化を指定してビルドします。
c:\tmp> cl /O2 /FAs test6.c
生成されたアセンブリリストの抜粋です。
......
; 11 :
; 12 : ans = div(divd, divr); // 割り算をして商と余りを構造体で返却
mov eax, DWORD PTR _divd$[esp+28]
cdq
idiv DWORD PTR _divr$[esp+28]
mov edi, eax
mov eax, DWORD PTR _divd$[esp+28]
cdq
idiv DWORD PTR _divr$[esp+28]
; 13 :
; 14 : printf("q=%d, r=%d\n", ans.quot, ans.rem);
push edx
push edi
push OFFSET ??_C@_0M@CMCCKACO@q?$DN?$CFd?0?5r?$DN?$CFd?6@
call _printf
add esp, 28 ; 0000001cH
......
「/」と「%」を使った「test4.c」と同様、div 関数を使った「test6.c」も、idiv 命令が無駄に 2 回発行されてしまいました。
Visual C++ の _div64 組み込み関数は idiv 命令と同じ?
Visual C++ のライブラリには、div 関数によく似た _div64 関数があります。組み込み関数 (intrinsics) と呼ばれるマシン語に直結するタイプの関数であり、書式は次の通りです。
#include <intrin.h>
int _div64(
__int64 dividend,
int divisor,
int* remainder
);
先の「test3.c」をベースに、「/」や「%」の代わりに _div64 関数を使った「test7.c」を書いてみます。
#include <stdio.h>
#include <intrin.h>
int main(int argc, char *argv[])
{
__int64 divd;
int divr, q, r;
divd = (__int64)atoi(argv[1]); // 割られる数
divr = atoi(argv[2]); // 割る数
q = _div64(divd, divr, &r); // 組み込み関数で商と余りを計算
printf("q=%d, r=%d\n", q, r);
return 0;
}
最適化なしでビルドします。
c:\tmp> cl /Od /FAs test7.c
生成されたアセンブリリストの抜粋です。
......
; 12 : q = _div64(divd, divr, &r); // 組み込み関数で商と余りを計算
mov eax, DWORD PTR _divd$[ebp]
mov edx, DWORD PTR _divd$[ebp+4]
idiv DWORD PTR _divr$[ebp]
mov DWORD PTR _r$[ebp], edx
mov DWORD PTR _q$[ebp], eax
......
期待通り、idiv 命令はひとつだけです。関数呼び出しのオーバーヘッドもありません。コードは省略しますが、速度最適化を指定してビルドした場合も同様でした。
これまでどうしても div 命令が 2 回発行されてしまった scanf 関数を使ったバージョンはどうなるでしょうか。
先の「test4.c」をベースに、「/」や「%」の代わりに _div64 関数を使った「test8.c」を書きます。
#include <stdio.h>
#include <intrin.h>
int main(int argc, char *argv[])
{
__int64 divd;
int divr, q, r;
scanf("%lld", &divd); // 割られる数
scanf("%d", &divr); // 割る数
q = _div64(divd, divr, &r); // 組み込み関数で商と余りを計算
printf("q=%d, r=%d\n", q, r);
return 0;
}
最適化なしでビルドします。
c:\tmp> cl /Od /FAs test8.c
生成されたアセンブリリストの抜粋です。
; 12 : q = _div64(divd, divr, &r); // 組み込み関数で商と余りを計算
mov eax, DWORD PTR _divd$[ebp]
mov edx, DWORD PTR _divd$[ebp+4]
idiv DWORD PTR _divr$[ebp]
mov DWORD PTR _r$[ebp], edx
mov DWORD PTR _q$[ebp], eax
こちらも idiv 命令はひとつだけです。コードは省略しますが、速度最適化を指定してビルドした場合も同様でした。
結局、_div64 関数を使っておけば間違いなさそうです。
おわりに
割り算の商と余りを同時に計算する方法について見てきました。
今回試した範囲での結果をまとめます。
- x86/x64 の div 命令や idiv 命令は、商と余りを同時に計算できます。
- C 言語の「/」と「%」を使った 2 つの式を、最適化によりコンパイラがひとつの idiv 命令にまとめてくれることがあります。まとめてくれないこともあります。
- C 言語の div 関数は、ソースコード上は 1 文でも、内部的には idiv 命令が 2 回発行されてしまうことがあります。
- Visual C++ の _div64 組み込み関数は、最適化の有無にかかわらず idiv 命令を 1 回しか発行せず、安定的に高速に処理されます。ただしマクロソフト固有の関数になります。
参考文献
[1] Intel「Intel(R) 64 and IA-32 Architectures Software Developer’s Manual」, 2024