概要

Java には Math.abs( i ) というメソッドがあります。

これはアンチロックブレーキシステムでもなければ、アンバサ(Ambasa)の略でもなく、「Absolute Value」 = 絶対値を取る機能です。

例えばMath.abs( -500 ) と書くと、「500」が返ってきます。

/** 実行例 */
int i = Math.abs( -500 );
System.out.println( i );
  // => 500

ところで、このMath.absに、Integer.MIN_VALUE(Integerの中で最も小さな数 = -2147483648)を渡すとどうなるでしょう。

@CretedDate 2009/07/17 @Versions Java5

実際にやってみた

Javaのintは32ビットです。32ビットの数値が指す値は-2147483648~2147483647です。-2147483648 を abs すると、そのままマイナスが取れて 2147483648 が返ってきそうですが、この値はintの精度の中に納まっていません。

そのため、-2147483648を渡すとこうなります。

/** 実行例 */
  int i = Math.abs( -2147483648 );
  System.out.println( i );
  // => -2147483648

そのまま-2147483648が返ってきてますね。

どう見てもこれは絶対値じゃありません。

でも、戻り値がintである以上、2147483648は返しようがないので、正しい値が返らないのは仕方がないところではあります。これの為にわざわざunsigned intにしたり(Javaにはunsignedはないけど)、longにしたらちょっと馬鹿馬鹿しいですしね。

ちなみにこの動作はJavaの仕様的にこうなっているようです。

引数が Integer.MIN_VALUE の値 (int の最小値) と等しい場合は、結果も同じ値 (負の値) になります。

http://java.sun.com/javase/ja/6/docs/ja/api/java/lang/Math.html#abs%28int%29

試しに我が家のVisualC++で同じ動作をさせてみたところ、同じ結果になりました。Cの場合はこの動作については未定義で、各コンパイラに任されているそうです。

int _tmain(int argc, _TCHAR* argv[])
{
  int i = abs(-2147483648);
  std::cout << i;
    // => -2147483648
  return 0;
}

absの中の人は何してんだ?

恒例の中の人見学の時間です。absの処理は、内部的にはどんな風に行われているのでしょうか?

これがabsの中の人です。

/** absの中身 */
public static int abs(int a) {
  return (a < 0) ? -a : a;
}

変態的ソースが出てくると期待していた方はすいません。ごく普通のソースですね。

三項演算子で、0未満ならマイナスして返し、0以上ならそのまま返す。とても一般的なソースですね。

-2147483648 * -1 = -2147483648?

ところで、1つ不思議なこと(知っている人は不思議に思わないかもしれないけど)があります。

先ほどのソースを見る限り、absに-2147483648が引き渡された時は、-1が掛けられています。ということは、-2147483648 は -1 を掛けても -2147483648なんでしょうか?

実際に試してみましょう。

/** Integer.MIN_VALUE に -1 を掛けてみる */
int i = -2147483648 * -1;
System.out.println( i );
  // => -2147483648

予定通りの結果になりました。マイナス1を掛けても同じ数字のままです。

Javaの32ビットによる数値表現において、-1を掛けるということは、2の補数を取るということと同義です。

では、-2147483648の2進数表記はどんな値になるでしょう?

/** Integer.MIN_VALUE の2進数表記 */
int i = -2147483648;
System.out.println( Integer.toBinaryString( i ) );
  // => 10000000000000000000000000000000

10000000000000000000000000000000になるそうです。

32ビットの先頭だけが「1」で、残りは全部「0」という表記です。

これの2の補数はどういった値になるでしょうか?

2の補数は、全ビットを反転して1を足せば取れます。まず反転すると下記のようになります。

01111111111111111111111111111111

これに1を足すと、右から1ビット目~31ビット目が全部1なので、32ビットまで繰り上がって、結果はこうなります。

10000000000000000000000000000000

見事に元の値ですね。

総評

というわけで、-2147483648を使うときは気をつけましょう、というお話でした。

Integer.MIN_VALUEだろうが、MAX_VALUEだろうが、その辺の値は1足したり引いたりするだけで溢れる、オーバーフロー一歩手前の子なので、こんなレアケースよりもっと危険なことが溢れている気もしますが。