整数の割り算では商と余りを同時に使いたい場面が意外と多いです。
C言語には、商と余りを一度に計算し構造体で返すdivとldivが用意されています。
本記事では、2回の割り算を避けて効率よく正確に結果を得る方法として、使い方、返り値の取り扱い、負の数の挙動、32bit/64bit環境差まで丁寧に解説します。
divとldivとは?商と余りを一度に取得
div/ldivでできることとメリット
divとldivは、整数の割り算に対して商(quotient)と余り(remainder)を同時に計算し、まとめて返す関数です。
通常、商は演算子/、余りは演算子%で個別に求めますが、2回の割り算は可読性やパフォーマンスの点で不利になることがあります。
div/ldivを使うと次の利点があります。
- 一度の呼び出しで商と余りを取得でき、コードの意図が明確になります。
- コンパイラが最適化してくれる場合が多いですが、呼び出し側で重複計算を避けられるため、明示的に効率的です。
- 負の数を含む場合の商と余りの関係が一貫し、
a == b * quot + remという関係が常に成り立ちます。
返り値の構造体(div_t, ldiv_t)の基本
両関数は、専用の構造体を返します。
構造体のメンバ名は共通でquot(商)とrem(余り)です。
型だけが異なります。
divはdiv_t(中身はint相当のquot/rem)ldivはldiv_t(中身はlong相当のquot/rem)
この「まとめて返す」仕様により、商と余りを取り違えるミスや、計算途中の値の破壊を避けやすくなります。
下の表に特徴を整理します。
| 関数 | 引数の型 | 返り値の構造体 | 構造体のメンバ型 | 主な用途 |
|---|---|---|---|---|
| div | int, int | div_t | int | 通常の範囲の整数 |
| ldiv | long, long | ldiv_t | long | より大きい整数やlong前提のAPIに合わせる |
どんな場面で使うか
ファイル名に通し番号を振る、配列のブロック分割、ページング、時刻の時分秒の分解など、商と余りを同時に必要とする処理は多数あります。
特に、負の値を扱う場合や型が混在する場合に、div/ldivの明確さはバグ予防に役立ちます。
div/ldivの使い方
ヘッダはstdlib.hにある
宣言は#include <stdlib.h>で提供されます。
プロトタイプは次の通りです。
// <stdlib.h> より
div_t div(int numer, int denom);
ldiv_t ldiv(long int numer, long int denom);
浮動小数点の割り算ではありません。
整数型専用です。
結果の取り出し方
返り値は構造体ですので、result.quotとresult.remで取り出します。
C99以降では、整数の割り算はゼロ方向への切り捨てで、余りremは被除数(numer)と同じ符号になります。
常にnumer == denom * quot + remが成立します。
サンプル(結果の分解と検算)
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int a = 23;
int b = 7;
// 商と余りをまとめて取得
div_t r = div(a, b);
// 結果の取り出し
printf("quot = %d, rem = %d\n", r.quot, r.rem);
// 恒等式: a == b * quot + rem
printf("%d == %d * %d + %d は %s\n",
a, b, r.quot, r.rem,
(a == b * r.quot + r.rem) ? "真" : "偽");
return 0;
}
quot = 3, rem = 2
23 == 7 * 3 + 2 は 真
例: divで12/5の商と余りを取得
最も基本的な例です。
フォーマット指定子は%dを使います。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int a = 12;
int b = 5;
div_t r = div(a, b); // 12 / 5
printf("a = %d, b = %d\n", a, b);
printf("quot = %d, rem = %d\n", r.quot, r.rem);
printf("検算: %d = %d * %d + %d\n", a, b, r.quot, r.rem);
return 0;
}
a = 12, b = 5
quot = 2, rem = 2
検算: 12 = 5 * 2 + 2
例: ldivで大きな値の商と余りを取得
ldivはlong用です。
環境によってlongのサイズが異なる(後述)ため、ここでは実行環境に合わせて安全な値を選ぶ例を示します。
フォーマット指定子は%ldです。
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
int main(void) {
long numer;
long denom;
// 実行環境に応じて値を選択
if (sizeof(long) > sizeof(int)) {
// 例: LP64系(UNIX系の多く)ではlongは64bit
numer = 1000000000000L; // 1e12 (intの範囲を超えるがlongに収まる)
denom = 4096L;
} else {
// 例: ILP32/LLP64(Windows 64bit含む)ではlongは32bit
numer = 2147483640L; // intの最大付近だがlongにも収まる
denom = 4096L;
}
ldiv_t r = ldiv(numer, denom);
printf("sizeof(long) = %zu bytes\n", sizeof(long));
printf("numer = %ld, denom = %ld\n", numer, denom);
printf("quot = %ld, rem = %ld\n", r.quot, r.rem);
printf("検算: %ld = %ld * %ld + %ld\n", numer, denom, r.quot, r.rem);
return 0;
}
出力例(64bit longの環境例):
sizeof(long) = 8 bytes
numer = 1000000000000, denom = 4096
quot = 244140625, rem = 0
検算: 1000000000000 = 4096 * 244140625 + 0
出力例(32bit longの環境例):
sizeof(long) = 4 bytes
numer = 2147483640, denom = 4096
quot = 524287, rem = 4088
検算: 2147483640 = 4096 * 524287 + 4088
例: 負の数の割り算
C99以降の規則で、商はゼロ方向への切り捨て、余りは被除数と同符号です。
3パターンを比較します。
#include <stdio.h>
#include <stdlib.h>
static void show_div(int a, int b) {
div_t r = div(a, b);
printf("div(%d, %d): quot = %d, rem = %d | 検算: %d = %d * %d + %d\n",
a, b, r.quot, r.rem, a, b, r.quot, r.rem);
}
int main(void) {
show_div(-7, 3); // 被除数が負
show_div(7, -3); // 除数が負
show_div(-7, -3); // 両方負
return 0;
}
div(-7, 3): quot = -2, rem = -1 | 検算: -7 = 3 * -2 + -1
div(7, -3): quot = -2, rem = 1 | 検算: 7 = -3 * -2 + 1
div(-7, -3): quot = 2, rem = -1 | 検算: -7 = -3 * 2 + -1
余りが常に非負であるとは限らない点に注意してください。
数学で学ぶユークリッド除算の「非負の余り」とは異なる規則です。
divとldivの違いと選び方
型の違い(intとlong)と返り値
divはintでの割り算に、ldivはlongでの割り算に使います。
返る構造体もそれぞれdiv_tとldiv_tで、quot/remの型が異なります。
演算対象の型に合わせるのが基本です。
どちらを使う?値の範囲で決める
- 変数が
intならdiv、longならldivを選びます。 - 入力値が
intの範囲を超える可能性があるならlongまたはlong longを検討します。
どうしても最低でも64bitが必要な場面ではlongのビット幅に依存しないようlong longとlldivの利用を検討すると安全です(参考)。
32bit/64bit環境でのlongのサイズに注意
longのサイズは実装依存です。
代表的なデータモデルは次の通りです。
| データモデル | 代表的な環境 | int | long | ポインタ |
|---|---|---|---|---|
| ILP32 | 32bit Linux, 32bit Windows | 32bit | 32bit | 32bit |
| LP64 | 64bit Linux, macOS, BSD | 32bit | 64bit | 64bit |
| LLP64 | 64bit Windows | 32bit | 32bit | 64bit |
Windowsの64bitではlongが32bitのまま(LLP64)であるため、大きい整数の計算にldivを使っても幅は広がらない点に注意します。
確実に64bitが欲しい場合はlong long系(例:lldiv)を検討してください。
div/ldivの注意点と落とし穴
0で割らない
除数が0の場合の動作は未定義です。
必ずゼロ除算を避けるチェックを入れてください。
#include <stdio.h>
#include <stdlib.h>
int safe_div_int(int a, int b, div_t *out) {
if (b == 0) return 0; // 0なら失敗を返す
*out = div(a, b);
return 1;
}
int main(void) {
div_t r;
if (safe_div_int(10, 0, &r)) {
printf("quot=%d, rem=%d\n", r.quot, r.rem);
} else {
printf("除数が0です。計算を中止しました。\n");
}
return 0;
}
除数が0です。計算を中止しました。
負の余りの符号と切り捨て
C99以降ではゼロ方向への切り捨てで、余りは被除数と同符号です。
余りが負になっても正しいことを理解しておきましょう。
もし非負の余りが必要な(ユークリッド除算の)要件なら、用途に合わせて調整します。
// 非負の余りに正規化する一例(denom>0を仮定)
div_t euclid_div(int numer, int denom) {
div_t r = div(numer, denom);
if (r.rem < 0) {
// 余りを非負にし、商を1つ補正
r.rem += (denom > 0) ? denom : -denom;
r.quot -= (denom > 0) ? 1 : -1;
}
return r;
}
オーバーフローに注意
商または余りが表現できない場合、動作は未定義です。
特に2の補数表現の環境では、INT_MIN / -1やLONG_MIN / -1が危険です。
事前チェックで回避します。
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
int safe_div_overflow_int(int a, int b, div_t *out) {
if (b == 0) return 0;
// INT_MIN / -1 はintに収まらない可能性がある
if (a == INT_MIN && b == -1) return 0;
*out = div(a, b);
return 1;
}
int main(void) {
div_t r;
if (!safe_div_overflow_int(INT_MIN, -1, &r)) {
printf("この演算はオーバーフローの恐れがあるため中止しました。\n");
}
return 0;
}
この演算はオーバーフローの恐れがあるため中止しました。
型を混在させない
引数の型と関数の組合せを揃えることが大切です。
longの値にdivを使うと、狭い型へ暗黙変換されて値が失われる可能性があります。
フォーマット指定子(%dと%ld)の取り違えにも注意します。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
long a = 100000L;
long b = 300L;
// 悪い例: longにdiv(int,int)を使うと暗黙にintへ変換される
// div_t wrong = div(a, b); // 望ましくない
// 良い例: ldiv(long,long)を使う
ldiv_t ok = ldiv(a, b);
printf("quot=%ld, rem=%ld\n", ok.quot, ok.rem);
return 0;
}
quot=333, rem=100
「とりあえずキャストして通す」のは危険です。
値の範囲に合わせて適切な関数を選びましょう。
まとめ
商と余りを一度に取得できるdiv/ldivは、整数計算の定番ツールです。
構造体で返るquot/remを使えば、読みやすくバグの少ないコードを書けます。
使い方は#include <stdlib.h>の上で、div_t r = div(a, b);のように呼び、r.quotとr.remを取り出すだけです。
注意点として、ゼロ除算の禁止、負の余りの規則、オーバーフロー(INT_MIN/-1等)の回避、型整合を常に意識してください。
さらに、環境によりlongのビット幅が異なるため、必要な範囲に応じてdiv/ldivを選び、場合によってはlldivも検討します。
最後にもう一度、numer == denom * quot + remという関係は常に成立します。
この性質を活用し、安全で分かりやすい整数演算を行っていきましょう。
