整数の剰余は演算子%で書けますが、浮動小数点同士の剰余は同じようには書けません。
C言語では小数の剰余はfmod関数で計算します。
本記事では%との違い、fmodの使い方、注意点、そして実用的な活用例まで、初心者の方にも分かりやすく丁寧に解説します。
剰余は%とfmodの違い
整数の剰余は%のみ
整数型(intやlongなど)の剰余は演算子%で計算します。
浮動小数点型(floatやdouble)では%は使えません。
もし使おうとするとコンパイルエラーになります。
// このコードはコンパイルエラーになります(%は整数専用)
#include <stdio.h>
int main(void) {
double a = 5.5, b = 2.0;
// printf("%f\n", a % b); // エラー: invalid operands to binary %
return 0;
}
整数の剰余が必要なときは%を使い、小数の剰余が必要なときはfmodを使う、という役割分担をまず覚えておくと混乱しません。
以下に、整数と浮動小数点での剰余手段の対応を整理します。
| 値の種類 | 使うもの | 宣言場所 | 例 |
|---|---|---|---|
| 整数(int, long, など) | 演算子 % | 言語仕様(演算子) | 7 % 3 |
| 浮動小数点(float, double, long double) | 関数 fmod, fmodf, fmodl | #include <math.h> | fmod(5.5, 2.0) |
小数の剰余はfmodを使う
小数の剰余はfmod系の関数で求めます。
fmod(x, y)は、xをyで割った余りを返します。
後述するように、fmodの返り値の符号は第1引数(x)に一致します。
#include <stdio.h>
#include <math.h> // fmodはmath.hに宣言されています
int main(void) {
double x = 5.5, y = 2.0;
double r = fmod(x, y); // 期待値は1.5
printf("fmod(%.1f, %.1f) = %.1f\n", x, y, r);
return 0;
}
fmod(5.5, 2.0) = 1.5
fmodはmath.hの標準関数
fmodは標準Cライブラリのmath.hに定義された関数です。
C99以降の環境なら基本的に利用できます。
GCCやClangでは後述の通り-lmオプションで数学ライブラリへリンクが必要です。
fmodの使い方
書式: double fmod
基本形は次の通りです。
- 書式:
double fmod(double x, double y);
定義的にはr = x - trunc(x / y) * yで与えられ、rの符号はxと同じです。
ここでtruncは0方向への丸め(切り捨て)です。
floatはfmodf、long doubleはfmodl
実引数の型に合わせた関数が用意されています。
型が揃っていると不必要な型変換が避けられ、精度と可読性に良い影響があります。
float用:float fmodf(float x, float y);long double用:long double fmodl(long double x, long double y);
#include <stdio.h>
#include <math.h>
int main(void) {
float xf = 5.5f, yf = 2.0f;
float rf = fmodf(xf, yf); // float版
long double xl = 5.5L, yl = 2.0L;
long double rl = fmodl(xl, yl); // long double版
printf("fmodf(%.1f, %.1f) = %.1f\n", xf, yf, rf);
printf("fmodl(%.1Lf, %.1Lf) = %.1Lf\n", xl, yl, rl);
return 0;
}
fmodf(5.5, 2.0) = 1.5
fmodl(5.5, 2.0) = 1.5
基本例: fmod(5.5, 2.0)の結果
最もシンプルな例として、fmod(5.5, 2.0)は1.5を返します。
割り算5.5 / 2.0 = 2.75の整数部分2を使い5.5 - 2 * 2.0 = 1.5というイメージです。
#include <stdio.h>
#include <math.h>
int main(void) {
printf("%.1f\n", fmod(5.5, 2.0));
return 0;
}
1.5
GCCやClangは-lmでリンク
Unix系環境のGCCやClangでは、数学ライブラリにリンクするため-lmが必要です。
WindowsのMSVCでは不要な場合が多いです。
# GCC
gcc -std=c17 -O2 demo.c -lm -o demo
# Clang
clang -std=c17 -O2 demo.c -lm -o demo
fmodの注意点と挙動
負の数の結果の符号は第1引数と同じ
fmodの返り値の符号は必ず第1引数(x)の符号になります。
これは整数の%と同じ規則です。
正規化(0〜正の範囲に収める)のときは、負の結果への追加処理が必要です。
#include <stdio.h>
#include <math.h>
int main(void) {
printf("fmod(-5.5, 2.0) = %f\n", fmod(-5.5, 2.0)); // -1.5
printf("fmod( 5.5, -2.0) = %f\n", fmod( 5.5, -2.0)); // 1.5
printf("fmod(-5.5, -2.0) = %f\n", fmod(-5.5, -2.0)); // -1.5
// -0.0の可能性にも注意
double r = fmod(-4.0, 2.0); // 余りが0だが符号はxと同じ => -0.0になり得る
printf("fmod(-4.0, 2.0) = %f\n", r);
return 0;
}
fmod(-5.5, 2.0) = -1.500000
fmod( 5.5, -2.0) = 1.500000
fmod(-5.5, -2.0) = -1.500000
fmod(-4.0, 2.0) = -0.000000
-0.0を避けたい場合は、if (r == 0.0) r = 0.0;のように0を代入して正の0.0に揃えることができます。
除数が0.0はNaN(非数)になる
除数yが0.0の場合、fmod(x, 0.0)はNaN(非数)になります。
浮動小数点の例外(FE_INVALID)が発生する実装もあります。
演算前に0除算を避ける防御コードを書きましょう。
#include <stdio.h>
#include <math.h>
int main(void) {
double x = 1.23;
double y = 0.0;
double r = fmod(x, y); // 結果はNaN
printf("r = %f, isnan(r) = %d\n", r, isnan(r)); // isnanはNaN判定
return 0;
}
r = nan, isnan(r) = 1
丸め誤差に注意
浮動小数点は2進数で表現されるため、10進のきれいな小数を正確に表せないことがあります。
その結果、本来0になるはずと考えた剰余が、極小の非0になったり、期待と違う値になることがあります。
#include <stdio.h>
#include <math.h>
int main(void) {
// 10回の0.1がちょうど1.0になれば剰余は0のはずだが、0.1は2進では正確に表せない
double r1 = fmod(1.0, 0.1);
// 0.3は0.1の3倍と期待して0を想像しがちだが、実際には厳密に等しくならない
double r2 = fmod(0.3, 0.1);
printf("fmod(1.0, 0.1) = %.17g\n", r1);
printf("fmod(0.3, 0.1) = %.17g\n", r2);
// 近似比較の例: しきい値(イプシロン)以下なら0とみなす
const double eps = 1e-12;
printf("|r1| < eps ? %s\n", fabs(r1) < eps ? "true" : "false");
printf("|r2| < eps ? %s\n", fabs(r2) < eps ? "true" : "false");
return 0;
}
出力例(環境により多少異なります):
fmod(1.0, 0.1) = 0.1
fmod(0.3, 0.1) = 0.1
|r1| < eps ? false
|r2| < eps ? false
上記のように、10進の直感と異なる結果になることがあります。
剰余が0かどうかの判定は、絶対値が小さいか(しきい値以下か)で判定するのが現実的です。
fmodの活用例
角度を0〜360に正規化する
角度がマイナスや360以上になることはよくあります。
0以上360未満の範囲に収めるにはfmodと加算で調整します。
#include <stdio.h>
#include <math.h>
static double normalize_deg(double deg) {
double r = fmod(deg, 360.0); // 余り。ここでは[-360, 360)の範囲
if (r < 0.0) r += 360.0; // 負なら一周分を足して[0, 360)へ
if (r == 0.0) r = 0.0; // -0.0対策(任意)
return r;
}
int main(void) {
double cases[] = { 10.0, 370.0, -30.0, -720.0, 720.0 };
for (size_t i = 0; i < sizeof(cases)/sizeof(cases[0]); ++i) {
printf("normalize_deg(%7.1f) = %6.1f\n", cases[i], normalize_deg(cases[i]));
}
return 0;
}
normalize_deg( 10.0) = 10.0
normalize_deg( 370.0) = 10.0
normalize_deg( -30.0) = 330.0
normalize_deg( -720.0) = 0.0
normalize_deg( 720.0) = 0.0
0.0〜1.0に収めるラッピング
テクスチャ座標や進捗率の計算など、値を[0.0, 1.0)の範囲にラップしたいときにもfmodが便利です。
#include <stdio.h>
#include <math.h>
// 値xを[0, 1)にラップする
static double wrap01(double x) {
double r = fmod(x, 1.0);
if (r < 0.0) r += 1.0; // 負のときは+1.0
if (r == 0.0) r = 0.0; // -0.0対策(任意)
return r;
}
int main(void) {
double xs[] = { -2.3, -0.1, 0.0, 0.25, 0.99, 1.0, 2.75 };
for (size_t i = 0; i < sizeof(xs)/sizeof(xs[0]); ++i) {
printf("wrap01(%5.2f) = %.8f\n", xs[i], wrap01(xs[i]));
}
return 0;
}
wrap01(-2.30) = 0.70000000
wrap01(-0.10) = 0.90000000
wrap01(0.00) = 0.00000000
wrap01(0.25) = 0.25000000
wrap01(0.99) = 0.99000000
wrap01(1.00) = 0.00000000
wrap01(2.75) = 0.75000000
周期的な処理のオフセット計算
一定周期で繰り返す処理では、現在時刻が周期の中でどこに位置するかを余りで求めます。
負の時刻があり得る場合でも、上記の正規化パターンを使えば扱いやすくできます。
#include <stdio.h>
#include <math.h>
// 周期period内のオフセット[0, period)を返す
static double phase_offset(double t, double period) {
double r = fmod(t, period);
if (r < 0.0) r += period; // 負の時間も正規化
if (r == 0.0) r = 0.0; // -0.0対策(任意)
return r;
}
int main(void) {
const double period = 2.5; // 2.5秒周期
double times[] = { -0.7, 0.0, 0.7, 2.5, 5.1, 6.3, 10.0 };
printf("period = %.2f\n", period);
for (size_t i = 0; i < sizeof(times)/sizeof(times[0]); ++i) {
double t = times[i];
printf("t = %5.2f -> offset = %.3f\n", t, phase_offset(t, period));
}
return 0;
}
period = 2.50
t = -0.70 -> offset = 1.800
t = 0.00 -> offset = 0.000
t = 0.70 -> offset = 0.700
t = 2.50 -> offset = 0.000
t = 5.10 -> offset = 0.100
t = 6.30 -> offset = 1.300
t = 10.00 -> offset = 0.000
整数インデックスの循環なら%でも良いですが、実時間のような小数の周期ではfmodが適切です。
まとめ
本記事では、整数の剰余は%、小数の剰余はfmodという基本から、fmodの使い方、符号規則(返り値の符号は第1引数と同じ)、0除算でNaNになる点、そして浮動小数点特有の丸め誤差まで解説しました。角度の正規化や[0, 1)ラッピング、周期オフセットの計算など、fmodは実用場面が非常に多い関数です。
GCCやClangでは-lmを忘れずにリンクし、負の結果や-0.0、比較時のイプシロンなどの落とし穴にも配慮して使いこなしましょう。
