数値演算の「オーバーフロー」とは
C#において、intやlongといった整数型には、格納できる数値の範囲に上限(int.MaxValueなど)と下限(int.MinValueなど)があります。
もし計算結果がこの最大値を超えたり、最小値を下回ったりした場合、その現象を「オーバーフロー(桁あふれ)」と呼びます。
デフォルトの動作:サイレント・ラップアラウンド
C#のデフォルト設定では、パフォーマンスを優先するため、整数演算のオーバーフローはチェックされません。
オーバーフローが発生すると、値は単純に「ラップアラウンド(循環)」し、int.MaxValueに1を足すとint.MinValue(大きな負の数)になってしまいます。これはバグの重大な原因となりますが、エラーとしては通知されません。
using System;
public class DefaultOverflowExample
{
public static void Main()
{
// ほぼ int の最大値 (約21億)
int score = int.MaxValue - 50;
int bonus = 100;
Console.WriteLine($"元のスコア: {score}");
// 100 を足すと int.MaxValue を超えてしまう
// デフォルトではチェックされない
score = score + bonus;
// 期待値 (2147483697) とは異なり、負の値(-2147483599)になる
Console.WriteLine($"ボーナス後のスコア (オーバーフロー): {score}");
}
}
出力結果(例):
元のスコア: 2147483597
ボーナス後のスコア (オーバーフロー): -2147483599
checked ブロック:演算の監視
このようなサイレント・オーバーフローを防ぎ、問題が発生したことをプログラムに通知させたい場合、checkedキーワードを使用します。
checkedブロック({...})で囲まれた範囲内で行われる整数演算は、CLR(Common Language Runtime)によって厳密に監視されます。もし、そのブロック内でオーバーフローが発生した場合、プログラムはSystem.OverflowExceptionという実行時例外をスローします。
checked ブロックのコード例
先ほどの例をtry-catchとcheckedブロックを使って書き直します。
using System;
public class CheckedBlockExample
{
public static void Main()
{
int score = int.MaxValue - 50;
int bonus = 100;
Console.WriteLine($"元のスコア: {score}");
try
{
// このブロック内の演算を監視する
checked
{
score = score + bonus; // ここでオーバーフローが発生
}
Console.WriteLine($"ボーナス後のスコア: {score}");
}
catch (OverflowException e)
{
Console.WriteLine($"--- エラー発生 ---");
// 例外を捕捉できる
Console.WriteLine(e.Message);
}
}
}
出力結果:
元のスコア: 2147483597
--- エラー発生 ---
Arithmetic operation resulted in an overflow. (算術演算の結果オーバーフローが発生しました。)
checkedブロックのおかげで、値が不正にラップアラウンドするのを防ぎ、catchブロックで問題を検知して安全に処理を分岐させることができました。
checked 式:単一の演算を監視
監視したい演算が1行だけの場合、checkedブロック({...})よりも簡潔な「checked式」の構文(checked(...))を使用できます。
checked( ... )の括弧内に、監視したい演算式を記述します。
checked 式のコード例
using System;
public class CheckedExpressionExample
{
public static void Main()
{
int score = int.MaxValue - 50;
int bonus = 100;
Console.WriteLine($"元のスコア: {score}");
try
{
// (score + bonus) という式だけを監視する
score = checked(score + bonus);
Console.WriteLine($"ボーナス後のスコア: {score}");
}
catch (OverflowException e)
{
Console.WriteLine($"--- エラー発生 ---");
Console.WriteLine(e.Message);
}
}
}
出力結果:
元のスコア: 2147483597
--- エラー発生 ---
Arithmetic operation resulted in an overflow. (算術演算の結果オーバーフローが発生しました。)
補足:unchecked キーワード
checkedとは逆に、uncheckedキーワードも存在します。これは、演算のオーバーフローチェックを意図的に無効にし、デフォルトのラップアラウンド動作を強制します。
通常、C#のデフォルトはunchecked(チェックしない)状態ですが、プロジェクトのコンパイル設定で「演算のオーバーフロー/アンダーフローをチェックする」を有効にしている場合、checkedがプロジェクト全体のデフォルトになります。その際、特定の箇所だけ意図的にラップアラウンドさせたい場合にuncheckedキーワードが使用されます。
注意点:浮動小数点数の場合
checkedキーワードは、intやlongなどの整数型の演算に対してのみ機能します。
doubleやfloatといった浮動小数点数型は、オーバーフローしてもOverflowExceptionをスローしません。代わりに、double.PositiveInfinity(正の無限大)やdouble.NegativeInfinity(負の無限大)といった特別な値を取るため、checkedの対象外となります。
まとめ
checkedキーワードは、C#における整数演算の安全性を高めるための重要な機能です。
- デフォルト(
unchecked): パフォーマンスは高いが、オーバーフローは検知されず値がラップアラウンドする。 checked: 演算を監視し、オーバーフロー発生時にOverflowExceptionをスローする。
外部からの入力値(ユーザー入力やAPIからのデータ)など、予期しない値によってオーバーフローが発生しうる可能性のある箇所や、金融計算など、計算の正確性が非常に重要なロジックにおいては、checkedコンテキストを使用して演算を保護することが推奨されます。
