小数点以下の切り上げと切り捨ては、C言語で数値を意図通りに丸めるための基本テクニックです。
本記事ではceilとfloorの使い方を、負の数での挙動やコンパイル方法、型の注意点まで丁寧に解説します。
実務でよくあるページ計算や料金の端数処理なども実例で示します。
切り上げ・切り捨てとは
ceilの基本
ceil(x)は「x以上の最小の整数」を返します。
戻り値の型はdoubleです。
整数値といっても実際にはdoubleとして返る点に注意します。
- 例:
ceil(3.2) == 4.0、ceil(3.0) == 3.0
実務では「必要なページ数」や「必要な箱数」のように、不足分があれば1つ繰り上げたい用途で多用します。
floorの基本
floor(x)は「x以下の最大の整数」を返します。
こちらも戻り値はdoubleです。
- 例:
floor(3.8) == 3.0、floor(3.0) == 3.0
座標やピクセルのインデックス化など、小数を常に下側へ丸めたい場合に向いています。
負の数での挙動
負の数では直感と逆方向に見える場合があるため注意が必要です。
ceilは「上へ」ではなく「以上の最小」、floorは「以下の最大」という定義で考えると混乱しにくくなります。
以下の表で違いを確認します。
| x | ceil(x) | floor(x) | (int)x へのキャスト |
|---|---|---|---|
| 3.2 | 4.0 | 3.0 | 3 |
| -3.2 | -3.0 | -4.0 | -3 |
| 3.0 | 3.0 | 3.0 | 3 |
| -3.0 | -3.0 | -3.0 | -3 |
負の数ではfloor(-3.2) == -4.0となり、より小さい(より負側の)整数へ動く点が重要です。
基本の使い方
math.hをインクルードする
ceilとfloorは標準ライブラリ<math.h>に宣言されています。
利用するにはヘッダをインクルードします。
// ceil, floor を使うのに必要
#include <math.h>
// 入出力のため
#include <stdio.h>
gccは-lmを付けてコンパイル
多くの環境(特にLinuxやmacOS)のGCCでは、数値計算ライブラリを明示的にリンクする必要があります。
-lmを忘れるとリンクエラーになります。
# 例: C11 で警告ON、最適化ON、mathライブラリをリンク
gcc -std=c11 -Wall -Wextra -O2 example.c -lm
MSVC(Windows)など一部の環境では-lmは不要です。
関数の型
代表的な関数の宣言は次のとおりです。
戻り値はいずれも整数値相当ですが型は浮動小数点です。
| 関数名 | プロトタイプ | 説明 |
|---|---|---|
| ceil | double ceil(double x); | x以上の最小の整数をdoubleで返す |
| floor | double floor(double x); | x以下の最大の整数をdoubleで返す |
| ceilf | float ceilf(float x); | float版 |
| floorf | float floorf(float x); | float版 |
| ceill | long double ceill(long double x); | long double版 |
| floorl | long double floorl(long double x); | long double版 |
引数がfloatでもceilに渡すとdoubleへ拡張されます。
パフォーマンスや型の一貫性を重視する場合はceilf/floorfを選びます。
まずは基本的な使い方を確認します。
#include <stdio.h>
#include <math.h>
int main(void) {
// テストする値
double values[] = { 3.2, 3.8, 3.0, -3.2, -3.8, -3.0 };
size_t n = sizeof(values) / sizeof(values[0]);
for (size_t i = 0; i < n; ++i) {
double x = values[i];
// ceil と floor は double を返します
double up = ceil(x); // 切り上げ
double down = floor(x); // 切り捨て
// %.1f で小数1桁、%.0f で小数0桁の表示
printf("x = %6.1f -> ceil(x) = %4.0f, floor(x) = %4.0f\n", x, up, down);
}
return 0;
}
x = 3.2 -> ceil(x) = 4, floor(x) = 3
x = 3.8 -> ceil(x) = 4, floor(x) = 3
x = 3.0 -> ceil(x) = 3, floor(x) = 3
x = -3.2 -> ceil(x) = -3, floor(x) = -4
x = -3.8 -> ceil(x) = -3, floor(x) = -4
x = -3.0 -> ceil(x) = -3, floor(x) = -3
ceil,floorの注意点
intへのキャストとの違い
(int)xは小数部分の切り捨て(0方向への丸め)です。
これはfloorやceilと一致しない場合があります。
特に負の数で差が出ます。
#include <stdio.h>
#include <math.h>
int main(void) {
double a = 3.7;
double b = -3.7;
printf("a = %.1f: (int)a = %d, floor(a) = %.0f, ceil(a) = %.0f\n",
a, (int)a, floor(a), ceil(a));
printf("b = %.1f: (int)b = %d, floor(b) = %.0f, ceil(b) = %.0f\n",
b, (int)b, floor(b), ceil(b));
return 0;
}
a = 3.7: (int)a = 3, floor(a) = 3, ceil(a) = 4
b = -3.7: (int)b = -3, floor(b) = -4, ceil(b) = -3
負の数では(int)xとfloor(x)が異なる点に注意してください。
戻り値の型(double)に注意
ceil/floorの戻り値はdoubleです。
整数変数へ代入する場合は、範囲外や意図しない暗黙変換に注意します。
#include <stdio.h>
#include <math.h>
#include <limits.h> // INT_MAX, INT_MIN
int main(void) {
double x = 1234567890.1; // 十分大きい値
double y = 1e12; // int には入らない可能性が高い
double cx = ceil(x);
double cy = ceil(y);
// 安全に int へ変換する例
if (cx <= INT_MAX && cx >= INT_MIN) {
int ix = (int)cx; // 明示的キャスト
printf("ceil(%.1f) -> %d (intに収まりました)\n", x, ix);
} else {
printf("ceil(%.1f) は int の範囲外です\n", x);
}
if (cy <= INT_MAX && cy >= INT_MIN) {
int iy = (int)cy;
printf("ceil(%.0f) -> %d (intに収まりました)\n", y, iy);
} else {
printf("ceil(%.0f) は int の範囲外です\n", y);
}
return 0;
}
ceil(1234567890.1) -> 1234567891 (intに収まりました)
ceil(1000000000000) は int の範囲外です
整数型へ詰めるなら範囲チェックを行うのが安全です。
long longなどより広い型が必要な場合もあります。
浮動小数点の誤差に注意
浮動小数点は2進数で近似表現されるため、見た目の値と内部値がわずかにズレることがあります。
このズレがceil・floorの結果に影響する場合があります。
#include <stdio.h>
#include <math.h>
int main(void) {
double a = 0.1;
double b = 0.2;
double s = a + b; // 多くの環境で 0.30000000000000004 になる
printf("s = a + b = %.17f\n", s);
printf("s * 10 = %.17f\n", s * 10);
printf("ceil(s * 10) = %.0f\n", ceil(s * 10)); // 4 になることがある
printf("floor(s * 10) = %.0f\n", floor(s * 10)); // 3
// 対策例1: 目的に応じて微小量を補正 (問題に応じて値は調整)
double eps = 1e-12;
printf("ceil(s * 10 - eps) = %.0f\n", ceil(s * 10 - eps));
// 対策例2: 可能なら整数演算へ置き換える
int sa = 1, sb = 2; // 0.1 を 1、0.2 を 2 として「10倍の整数」で扱うイメージ
int s10 = sa + sb; // 1 + 2 = 3 (10倍スケール)
printf("整数スケールでの合計(10倍) = %d -> そのまま3\n", s10);
return 0;
}
s = a + b = 0.30000000000000004
s * 10 = 3.0000000000000004
ceil(s * 10) = 4
floor(s * 10) = 3
ceil(s * 10 - eps) = 3
整数スケールでの合計(10倍) = 3 -> そのまま3
金額や個数など厳密さが必要な処理は、可能な限り整数にスケールして扱うのが実務的に安全です。
比較や条件分岐での扱い方
「すでに整数かどうか」を判定したい場面があります。
浮動小数の誤差を考慮し、許容誤差(イプシロン)を用いるのが堅実です。
#include <stdio.h>
#include <math.h>
#include <float.h>
int main(void) {
double xs[] = { 3.0, 2.9999999999999996, 3.0000000000000004, 3.2 };
size_t n = sizeof(xs) / sizeof(xs[0]);
for (size_t i = 0; i < n; ++i) {
double x = xs[i];
// 方法1: ceil と floor が一致するかを見る
int is_integer1 = (ceil(x) == floor(x));
// 方法2: 許容誤差で floor との差を見る (より実務的)
double eps = 1e-12; // 値の桁スケールに応じて調整
int is_integer2 = fabs(x - floor(x)) < eps || fabs(ceil(x) - x) < eps;
printf("x = %.17g -> is_integer1 = %d, is_integer2 = %d\n",
x, is_integer1, is_integer2);
}
return 0;
}
x = 3 -> is_integer1 = 1, is_integer2 = 1
x = 3 -> is_integer1 = 1, is_integer2 = 1
x = 3.0000000000000004 -> is_integer1 = 0, is_integer2 = 1
x = 3.2 -> is_integer1 = 0, is_integer2 = 0
誤差が混ざる可能性がある値には、厳密比較(==)ではなく許容誤差による比較を使うのが安全です。
よくある用途と実践例
ページ数を計算する
レコード数Nを1ページあたりper件で表示する場合、必要なページ数はceil((double)N / per)で求められます。
整数演算へ置き換えるとさらに安全です。
#include <stdio.h>
#include <math.h>
int main(void) {
int N = 101; // 総件数
int per = 20; // 1ページあたり
// 方法1: ceil を使う
int pages1 = (int)ceil((double)N / (double)per);
// 方法2: 完全整数演算 (おすすめ)
// (N + per - 1) / per は余りがあれば繰り上げになる整数の定石
int pages2 = (N + per - 1) / per;
printf("N=%d, per=%d -> pages1=%d, pages2=%d\n", N, per, pages1, pages2);
return 0;
}
N=101, per=20 -> pages1=6, pages2=6
整数演算の式(N + per - 1) / perはページ計算の定番で、浮動小数の誤差を回避できます。
料金の端数処理
税込価格を「1円未満切り上げ」などのルールで求めたい場合、ceilが使えます。
ただし金額計算は浮動小数の誤差が命取りなので、整数化するのが安全です。
#include <stdio.h>
#include <math.h>
int main(void) {
int price = 980; // 税抜(円)
double tax_rate = 0.1; // 10% 消費税
// 単純に ceil を使う(注意: 浮動小数誤差の影響を受ける可能性)
int total1 = (int)ceil(price * (1.0 + tax_rate));
// 安全策: 税率を整数(パーミルやパーセント)で扱い整数演算にする
// 例: 10% = 110/100 として、切り上げ(1円未満切り上げ)を整数で実現
int total2 = (price * 110 + 100 - 1) / 100; // (x + d - 1)/d は切り上げ
// 10円単位での切り上げ例 (例: 1の位切り上げ)
int total3 = ((price + 9) / 10) * 10;
printf("ceilで計算 : %d\n", total1);
printf("整数演算で安全に計算: %d\n", total2);
printf("10円単位で切り上げ : %d\n", total3);
return 0;
}
ceilで計算 : 1078
整数演算で安全に計算: 1078
10円単位で切り上げ : 990
金額計算は整数演算を基本にし、やむを得ず浮動小数を使う場合は十分な検証を行うことを強くおすすめします。
バッチ分割数を求める
N件の仕事をバッチサイズKで処理する場合、必要なバッチ数はceil((double)N / K)です。
これも整数演算へ置き換えられます。
#include <stdio.h>
#include <math.h>
int main(void) {
int N = 53;
int K = 8;
int batches1 = (int)ceil((double)N / K);
int batches2 = (N + K - 1) / K; // 整数版
printf("N=%d, K=%d -> batches1=%d, batches2=%d\n", N, K, batches1, batches2);
return 0;
}
N=53, K=8 -> batches1=7, batches2=7
座標やピクセル数の切り捨て
ピクセル座標は整数です。
小数座標を画像の画素インデックスに変換する際はfloorで下側に寄せるのが一般的です。
範囲外にならないようにクリップも合わせて行います。
#include <stdio.h>
#include <math.h>
static int clamp_int(int v, int lo, int hi) {
if (v < lo) return lo;
if (v > hi) return hi;
return v;
}
int main(void) {
double x = 123.7;
double y = -5.2;
int width = 200, height = 150;
// 画像インデックスへ (0 始まりを想定)
int ix = (int)floor(x);
int iy = (int)floor(y);
// 画像範囲に収める (0..width-1, 0..height-1)
ix = clamp_int(ix, 0, width - 1);
iy = clamp_int(iy, 0, height - 1);
printf("floor座標 -> (ix, iy) = (%d, %d)\n", ix, iy);
return 0;
}
floor座標 -> (ix, iy) = (123, 0)
負の座標は0未満になる可能性が高いため、インデックス化の前後で必ず範囲チェックを行います。
まとめ
ceilは「以上の最小の整数」、floorは「以下の最大の整数」を返し、戻り値はdoubleです。
負の数ではfloorがより負側へ動くなど直感と異なる点があるため、定義で理解することが大切です。
コンパイル時はGCCで-lmを付け、整数へキャストする際は範囲チェックも忘れないでください。
さらに、浮動小数点の誤差は現実のバグにつながるため、ページ数・バッチ数・金額など厳密さの必要な計算は可能な限り整数演算へ置き換えましょう。
これらのポイントを押さえれば、切り上げ・切り捨ては安全かつ確実に活用できるようになります。
