web-dev-qa-db-ja.com

Math.absがInteger.Min_VALUEに対して誤った値を返します

このコード:

System.out.println(Math.abs(Integer.MIN_VALUE));

-2147483648を返します

2147483648として絶対値を返すべきではありませんか?

76
user665319

Integer.MIN_VALUE-2147483648ですが、32ビット整数に含めることができる最大値は+2147483647です。 32ビット整数で+2147483648を表現しようとすると、-2147483648に効果的に「ロールオーバー」します。これは、符号付き整数を使用する場合、+2147483648-2147483648の2の補数バイナリ表現が同一であるためです。ただし、+2147483648は範囲外と見なされるため、これは問題ではありません。

この問題についてもう少し読むには、 ウィキペディアのTwoの補数に関する記事 を参照してください。

83
jonmorgan

あなたが指摘する行動は、実際、直感に反します。ただし、この動作は Math.abs(int)のjavadoc で指定された動作です。

引数が負でない場合、引数が返されます。引数が負の場合、引数の否定が返されます。

つまり、Math.abs(int)は次のJavaコードのように動作するはずです。

_public static int abs(int x){
    if (x >= 0) {
        return x;
    }
    return -x;
}
_

つまり、負の場合、_-x_です。

JLSセクション15.15.4 によると、_-x_は_(~x)+1_と等しく、ここで_~_はビットごとの補数演算子です。

これが正しいかどうかを確認するには、-1を例としてみましょう。

整数値_-1_は、Java(これをprintlnまたは他の方法で確認してください)の16進数の_0xFFFFFFFF_として記録できます。 -(-1)は次のようになります。

_-(-1) = (~(0xFFFFFFFF)) + 1 = 0x00000000 + 1 = 0x00000001 = 1
_

だから、それは動作します。

_Integer.MIN_VALUE_ で試してみましょう。最小の整数は_0x80000000_で表現できること、つまり、最初のビットを1に設定し、残りの31ビットを0に設定できることを知っていると、次のようになります。

_-(Integer.MIN_VALUE) = (~(0x80000000)) + 1 = 0x7FFFFFFF + 1 
                     = 0x80000000 = Integer.MIN_VALUE
_

そして、これがMath.abs(Integer.MIN_VALUE)が_Integer.MIN_VALUE_を返す理由です。また、_0x7FFFFFFF_は_Integer.MAX_VALUE_であることに注意してください。

そうは言っても、この直感に反する戻り値による問題を将来どのように回避できますか?

  • @ Bombeが指摘したように で、以前にintsをlongにキャストできました。しかし、私たちは

    • それらをintsにキャストし直します。Integer.MIN_VALUE == (int) Math.abs((long)Integer.MIN_VALUE)のため機能しません。
    • または、Math.abs(long)もあるので、_Long.MIN_VALUE_に等しい値でMath.abs(Long.MIN_VALUE) == Long.MIN_VALUEを決して呼び出さないことを期待して、longsに進みます。
  • BigInteger.abs() は常に正の値を返すため、どこでもBigIntegersを使用できます。これは、生の整数型を操作するよりも少し遅い、優れた代替手段です。

  • 次のように、Math.abs(int)の独自のラッパーを作成できます。

_/**
 * Fail-fast wrapper for {@link Math#abs(int)}
 * @param x
 * @return the absolute value of x
 * @throws ArithmeticException when a negative value would have been returned by {@link Math#abs(int)}
 */
public static int abs(int x) throws ArithmeticException {
    if (x == Integer.MIN_VALUE) {
        // fail instead of returning Integer.MAX_VALUE
        // to prevent the occurrence of incorrect results in later computations
        throw new ArithmeticException("Math.abs(Integer.MIN_VALUE)");
    }
    return Math.abs(x);
}
_
  • 整数ビット単位のANDを使用して上位ビットをクリアし、結果が負でないことを確認します。_int positive = value & Integer.MAX_VALUE_(本質的に_Integer.MAX_VALUE_から_0_ではなく_Integer.MIN_VALUE_にオーバーフローします)

最後に、この問題はしばらくの間知られているようです。たとえば、 対応するfindbugsルールに関するこのエントリ を参照してください。

32
bernard paulus

Java docが javadoc のMath.abs()に対して言っていることは:

引数がInteger.MIN_VALUE(最も負の表現可能なint値)の値と等しい場合、結果は負の同じ値になることに注意してください。

11
moe

期待する結果を確認するには、Integer.MIN_VALUEからlongへ:

System.out.println(Math.abs((long) Integer.MIN_VALUE));
2
Bombe

2147483648はJavaの整数に格納できません。そのバイナリ表現は-2147483648と同じです。

0
ymajoros

だが (int) 2147483648L == -2147483648正の等価物がない負の数が1つあるため、正の値はありません。 Long.MAX_VALUEでも同じ動作が見られます。

0
Peter Lawrey