この演習では、いろいろな数値型がオーバーフローしたとき、つまり算術演算やキャストなどの計算の結果として生じた値をその型が保持しきれないときの動作を示します。
byte、ushort、uint、ulong)は、一周してゼロに戻ります(その型の最大値に1を足した値が法として働きます)。ただし、大まかに言えばcheckedブロックの中に書かれている場合は別で、このときはOverflowExceptionのインスタンスがスローされます。intとlongも同じように振る舞いますが、それぞれint.MinValueとlong.minValueに戻る点だけが異なります。int one = 1;
checked
{
int expr = int.MaxValue + one; // overflow exception is thrown
}
// or
int expr2 = checked(int.MaxValue + one); // overflow exception is thrown
checkedの状態が適用されるのは、そのブロックに直接書かれた式だけです。呼び出された関数の中で起きたオーバーフローは捕捉されません。floatとdouble型は_無限大_の状態になり、これはfloat.IsInfinity()などで判定できます。double d = double.MaxValue;
d *= 2d;
Double.IsFinite(d)
// => false
decimal型の数値では、OverflowExceptionのインスタンスがスローされます。checkedブロックの中にあるuncheckedの効果を打ち消したいときや、コンパイラーの設定が使われているときには、それに対応するuncheckedキーワードがあります。例外がスローされずに起きるオーバーフローは問題になることがあります。エラーは早く報告できるほどよい、ということが一般に当てはまるからです。
intとfloatのオーバーフローの問題は、結果をlong、decimal、doubleのいずれかの型の変数に代入することで緩和できます。
コードにとって大きな整数が欠かせないなら、BigInteger型を使うという選択肢もあります。
当然ながら、整数が一周しても問題ない場面もあります。とくに符号なしの値ではその傾向があります。古典的な例がハッシュコードで、整数の幅を一種の法として使います。
コードベースでは、uintやulongを識別子として使うときにチェックを入れないことがよくあります。手間に見合わないと考えられているからです。非常に大きな値が関わることはないと、扱う領域から明らかな場合も同じです。ただし、教訓としてこちらを見てみましょう。