除算と剰余の整数オーバーフロー

プログラムでは整数の四則演算はよくよく注意しないと足をすくわれることがあります。
整数と呼んでるものが普通と違って範囲があって循環しているためです。最大値と最小値の絶対値が 1 ズレているのもいや〜な問題の元になります。

この 1 ズレのため除算で整数オーバーフローが発生します。除算でですよ。
int の最小値を -1 で割ったときに結果が表現できないためです。除算と剰余ではただこの 1 ケースのみオーバーフローが起きます。

このケースは仕様書にちゃんと書いてあります。仕様書によると checked では例外スロー。unchecked では「unchecked コンテキストでは、System.ArithmeticException (またはコンテキストでのサブクラス) がスローされるか、または左のオペランドを結果値としてオーバーフローを報告しないかは、実装で定義されます。」実装依存だそうです。では Visual Studio 2008 SP1 で試してみます。

「int x = int.MinValue / -1;」と書くとコンパイルエラーになります。下のように書くとコンパイルは通ります。おバカさんめ。

static void Test1()
{
    int m = int.MinValue;
    int n = m / -1; // OverflowException 発生
    Console.WriteLine( n );
}

実行すると unchecked なのに OverflowException が飛んできました。x86/x64 Debug/Release ぜーんぶ全部です。checked も当然同じです。
0 で割り算しようとしてしまうのは当然気を付けますが、-1 も実は注意が必要ということですね。


続いて剰余。こっちはクレイジーです。
「int x = int.MinValue % -1;」と書くと x は正常に 0 になります
ところが

static void Test2()
{
    int m = int.MinValue;
    int n = m % -1; // OverflowException 発生!!
    Console.WriteLine( n );
}

これを実行すると除算同様 OverflowException が飛んできます。ひどい。コンパイル時と実行時で違うんですね。
checked も x86/x64 Debug/Release 全部 OverflowException です。

仕様書によると「左オペランドが int または long の最小値で右オペランドが -1 の場合は System.OverflowException がスローされます。x / y が例外をスローしない場合に x % y が例外をスローすることはありません。」だそうです。x / y が例外をスローするなら x % y が例外をスローする、とは書いてませんが MS のコンパイラはそういう動きをしました。これを書いてないのは高速化のために実装の幅を残すためでしょうね。

そもそも 0 でいいのに。CPU では除算と剰余が一体なので、除算でオーバーフローが起きたら剰余もその結果に従うということのようです。x86 では CPU が例外を投げるようですが、x64 では 32bit 演算なのに結果は 64bit 幅なので、しょうがなく(?) JIT 後の機械語に int.MinValue % -1 かどうかチェックするコードが生成されていました。明らかにそうじゃない場合はチェックコードは生成されません。わざわざチェックコードを生成するなら x86 にチェックコードを加えて 0 を返せばよかったのに。

まあ、マイナス割るマイナスって一体何だかわかりませんが、こんなことがあるので注意が必要ですよというお話でした。