C#を用いて数値処理を行う際、小数点以下の値をどのように扱うかは非常に重要なテーマです。
特に「切り捨て」の処理は、単純に値を削るだけではなく、対象が正の数か負の数かによって期待される結果が異なるため、適切なメソッドの選択が欠かせません。
プログラミング初心者から中堅エンジニアまで、何気なく使っている「切り捨て」処理ですが、C#にはMath.Floor、Math.Truncate、そして型キャストによる方法など、複数のアプローチが存在します。
本記事では、これらの手法の細かな違いや、実務で役立つ使い分けのポイント、そして浮動小数点数特有の注意点について詳しく解説します。
C#における切り捨て処理の基本
C#で数値の小数点以下を切り捨てる主な方法は、標準ライブラリであるSystem.Mathクラスを利用することです。
このクラスには、数学的な計算をサポートする多くの静的メソッドが含まれています。
切り捨てにおいて最も頻繁に利用されるのは、以下の2つのメソッドです。
- Math.Floorメソッド
- Math.Truncateメソッド
一見するとどちらも同じように思えるかもしれませんが、負の値を扱う際の挙動が決定的に異なります。まずはそれぞれの基本的な特徴を見ていきましょう。
Math.Floorメソッドの特徴
Math.Floorメソッドは、指定した数値以下の最大の整数を返します。
数学的な意味での「床(Floor)」関数です。
例えば、1.5に対して適用すると、1.5以下の最大の整数である1が返されます。
しかし、負の数である-1.5に対して適用すると、-1.5以下の最大の整数は-2となるため、結果は-2になります。
このように、Math.Floorは「常に数値が小さくなる方向(数直線上の左方向)」へ値を丸める処理だと理解すると分かりやすいでしょう。
Math.Truncateメソッドの特徴
一方で、Math.Truncateメソッドは、数値の整数部分のみを残し、小数部分を単純に削除(切り捨て)します。
正の数である1.5に対して適用すると1が返されるのはMath.Floorと同じですが、負の数である-1.5に適用した場合、結果は-1になります。
これは、単純に0.5という断片を捨て去った結果です。
つまり、Math.Truncateは「常に0に近い方の整数へ値を丸める処理」と言い換えることができます。
Math.FloorとMath.Truncateの動作比較
これら2つのメソッドの挙動の違いを、具体的なコードと実行結果で確認してみましょう。
using System;
public class Program
{
public static void Main()
{
double posValue = 1.8;
double negValue = -1.8;
Console.WriteLine("--- 正の数の場合 (1.8) ---");
Console.WriteLine($"Math.Floor: {Math.Floor(posValue)}");
Console.WriteLine($"Math.Truncate: {Math.Truncate(posValue)}");
Console.WriteLine("\n--- 負の数の場合 (-1.8) ---");
Console.WriteLine($"Math.Floor: {Math.Floor(negValue)}");
Console.WriteLine($"Math.Truncate: {Math.Truncate(negValue)}");
}
}
実行結果は以下の通りになります。
--- 正の数の場合 (1.8) ---
Math.Floor: 1
Math.Truncate: 1
--- 負の数の場合 (-1.8) ---
Math.Floor: -2
Math.Truncate: -1
このように、正の数では同一の結果が得られますが、負の数では出力される値が1つ異なることがわかります。
金融系のシステムや在庫管理、ゲーム内の座標計算など、負の値を扱う可能性がある処理では、この違いがバグの原因となることが多いため、要件に合わせた選択が必須です。
どちらを使うべきか?の判断基準
基本的には、以下の基準で選択します。
| シナリオ | 推奨されるメソッド | 理由 |
|---|---|---|
| 単純に「0.5」などの端数を取り除きたい | Math.Truncate | 正負に関わらず、小数部分を無視する直感的な動作をするため。 |
| 数学的な意味での最小整数を求めたい | Math.Floor | 小さい方の整数へ丸めるという一貫した数学的定義に従うため。 |
| 在庫数や個数など、負にならない値 | どちらでも可 | 正の領域では結果が一致するため。 |
座標システムやタイマーの計算などでは、負の方向に丸めたい場合が多く、Math.Floorが好まれる傾向にあります。
逆に、ユーザー入力の整形などでは、直感的に「余計な部分を消す」という意味でMath.Truncateが適している場面が多いでしょう。
型キャストによる切り捨て
C#では、double型やdecimal型の値をint型やlong型に明示的にキャストすることで、小数点以下を切り捨てることができます。
double originalValue = 2.9;
int castedValue = (int)originalValue; // 2 になる
このキャストによる切り捨ての挙動は、Math.Truncateメソッドと同じです。
つまり、正の数ならそのまま整数部を取り出し、負の数なら0に近い方の整数になります。
キャストを使用する際の注意点
キャストは非常に高速で便利な方法ですが、以下の点に注意が必要です。
- オーバーフローのリスク:
doubleの範囲がintの最大値(約21億)を超えている場合、キャストを行うと意図しない値(オーバーフロー)になります。checkedコンテキストを使用していない限り、エラーは発生せずに壊れた値が代入されます。 - 精度の損失: 型そのものが変わってしまうため、計算の途中で使用すると後の計算精度に影響を与える可能性があります。
基本的には、最終的な結果として整数が必要な場合にのみキャストを使用し、計算の過程ではMath.Truncateなどで型を維持したまま処理することをお勧めします。
小数点第n位での切り捨て手法
「小数点以下をすべて捨てる」のではなく、「小数点第2位まで残して第3位以降を切り捨てたい」といったケースも実務では頻繁に発生します。
残念ながら、Math.FloorやMath.Truncateには、桁数を指定する引数は存在しません。
そのため、以下の手順で計算を行います。
- 10のn乗を掛けて、残したい桁を整数部に持ってくる
- 切り捨て処理を行う
- 再び10のn乗で割って、元のスケールに戻す
実装例
例えば、小数点第2位まで残す(第3位以下を切り捨てる)場合は、100を掛けて処理します。
public static double TruncateAtPrecision(double value, int digits)
{
double precision = Math.Pow(10, digits);
return Math.Truncate(value * precision) / precision;
}
// 使用例
double val = 1.23456;
double result = TruncateAtPrecision(val, 2); // 1.23
ただし、この手法には浮動小数点数特有の誤差問題がつきまといます。
金融計算など正確性が求められる場面では、doubleではなくdecimal型を使用し、同様のロジックを組むか、後述するMath.Roundのオプションを検討してください。
浮動小数点数(double/float)の精度問題
C#でdouble型(倍精度浮動小数点数)を扱う際、最も注意すべきなのが「内部的な表現誤差」です。
コンピューターは数値を2進数で管理しているため、0.1のような10進数ではキリの良い数字も、2進数では循環小数となり、完全には表現できません。
これが原因で、切り捨て処理の結果が直感と異なることがあります。
double value = 0.1 * 3; // 期待値は 0.3
Console.WriteLine(Math.Truncate(value * 10) / 10);
上記のコードにおいて、人間は0.1 * 3は0.3だと考えますが、実際には0.29999999999999998...のような値になることがあります。
この状態でMath.Truncateを適用すると、期待した0.3ではなく0.2が返されるという恐ろしいバグが発生します。
解決策:decimal型の利用
金額計算や、厳密な桁数管理が必要な場合は、必ずdecimal型を使用してください。
decimalは10進数ベースで数値を保持するため、人間が扱う10進数の計算において誤差が発生しません。
decimal val1 = 0.1m;
decimal val2 = 3m;
decimal result = val1 * val2; // 確実に 0.3m
Console.WriteLine(Math.Truncate(result * 10) / 10); // 確実に 0.3
decimalはdoubleよりも計算負荷がわずかに高いですが、2026年現在の一般的なハードウェア性能を考えれば、精度を優先すべき場面では迷わずdecimalを選択すべきです。
Math.Roundを用いた「切り捨て」の代替手法
C#のMath.Roundメソッドは通常「四捨五入(または銀行丸め)」に使用されますが、列挙型MidpointRoundingを指定することで、特定の方向への丸めを制御できます。
.NETの比較的新しいバージョン(.NET 6以降など)では、MidpointRounding.ToZeroやMidpointRounding.ToNegativeInfinityといったオプションが充実しています。
MidpointRounding.ToZero:Math.Truncateと同様の挙動MidpointRounding.ToNegativeInfinity:Math.Floorと同様の挙動
これらを使用すると、桁数指定も含めて1つのメソッドで記述できるため、コードの可読性が向上します。
decimal d = 1.23456m;
// 小数点第2位まで残して切り捨て (Truncate相当)
decimal truncated = Math.Round(d, 2, MidpointRounding.ToZero);
// 1.23
このように、Math.Roundを賢く使うことで、自前で100を掛けたり割ったりする処理を排除でき、計算ミスのリスクを大幅に減らすことが可能です。
文字列操作による切り捨て(非推奨)
時折、数値を文字列に変換してから正規表現やSubstringで切り捨てを行うコードを見かけますが、これはパフォーマンス面および保守面から見て非推奨です。
理由:
- ロケールの影響: 国や地域の設定(カルチャ)によっては、小数点がコンマ(
,)になることがあり、文字列操作が失敗する。 - オーバーヘッド: 数値から文字列への変換、そして数値への再変換は、計算コストが非常に高い。
- 可読性の低下: 数学的な意図がコードから読み取りにくくなる。
表示用として最後に整形するだけであればToString("F2")などの書式指定子が便利ですが、これも内部的には「丸め(四捨五入に近い動作)」が行われるため、純粋な切り捨てには適していません。
パフォーマンスと最適化のヒント
大量のデータをループ内で処理する場合、メソッド呼び出しのオーバーヘッドすら気になることがあります。
その場合、単純なキャスト((int)や(long))が最も高速です。
ただし、現代のJIT(Just-In-Time)コンパイラは非常に優秀であり、Math.Floorなどの基本的な数学関数は、CPUの命令セット(SSEやAVXなど)に直接マッピングされるインライン展開が行われることが多いです。
そのため、マイクロベンチマークで極端な差が出ない限りは、速度よりも「コードの意図(正の数か負の数か)」を重視してメソッドを選ぶべきです。
また、2026年時点の.NET環境では、Generic Math(ジェネリック数学)の活用も進んでいます。
独自の数値型や拡張ライブラリを作成する場合は、IFloatingPoint<TSelf>インターフェースなどを通じて、型に依存しない汎用的な切り捨てロジックを記述することも検討してみましょう。
まとめ
C#で小数点以下を切り捨てる手法には複数の選択肢があり、それぞれに明確な特性があります。
- Math.Floor:数値を小さい方の整数へ丸める。負の数の場合、絶対値が大きくなる方向に動く。
- Math.Truncate:単純に小数部分を取り除く。負の数の場合、0に近い方の整数へ動く。
- 型キャスト:
Math.Truncateと同様の動作だが、オーバーフローに注意。 - Math.Round (MidpointRounding指定):桁数指定を伴う切り捨てにおいて、最も安全で可読性の高い方法。
プログラムの要件が「正の数しか扱わない」のであればどれを使っても大差はありませんが、「負の数が発生し得るか」「必要な精度はどれくらいか(double vs decimal)」を常に意識することで、予期せぬ計算ミスを防ぐことができます。
浮動小数点数の丸め処理は、一見地味ですがシステムの信頼性を左右する重要な要素です。
本記事で紹介した内容を参考に、状況に応じた最適な切り捨て手法を選択してください。
