Java8 の目立たない変更として、Math クラスにオーバーフロー時には例外を投げるExact (正確な) 系メソッドが追加された。
いずれも int または long を引数に取るようにオーバーロードされている。
算術演算の結果がオーバフローする場合には ArithmeticException がスローされる。
加算、減算、乗算
addExact
Integer r = 2_147_483_647 + 1; // -2147483648 Math.addExact(2_147_483_647, 1); // ArithmeticException
subtractExact
Integer r = -2_147_483_648 - 1; // 2147483647 Math.subtractExact(-2_147_483_647, 1); // ArithmeticException
multiplyExact
Integer r = 100_000 * 100_000; // 1410065408 Math.multiplyExact(100_000, 100_000); // ArithmeticException
加算、減算のオーバーフロー判定
それぞれの実装は以下のようになっている。
public static int addExact(int x, int y) { int r = x + y; if (((x ^ r) & (y ^ r)) < 0) { throw new ArithmeticException("integer overflow"); } return r; }
加算がオーバーフローするのは結果の符号が双方の引数の符号と異なる場合。
public static int subtractExact(int x, int y) { int r = x - y; if (((x ^ y) & (x ^ r)) < 0) { throw new ArithmeticException("integer overflow"); } return r; }
減算がオーバーフローするのは引数が異なる符号で、結果がxの符号と異なる場合。
結果がゼロ以下かで判定しているため、結果的に最上位ビットの符号判定となる。
乗算のオーバーフロー判定
乗算のオーバーフロー判定は以下の実装になっている。
public static int multiplyExact(int x, int y) { long r = (long)x * (long)y; if ((int)r != r) { throw new ArithmeticException("integer overflow"); } return (int)r; }
int 同士の乗算がオーバーフローするかは結果で判断。
public static long multiplyExact(long x, long y) { long r = x * y; long ax = Math.abs(x); long ay = Math.abs(y); if (((ax | ay) >>> 31 != 0)) { if (((y != 0) && (r / y != x)) || (x == Long.MIN_VALUE && y == -1)) { throw new ArithmeticException("long overflow"); } } return r; }
long の場合は複雑。
インクリメント、デクリメント
インクリメント、デクリメント はそのまま最大をインクリメントしたら例外、最小をデクリメントしたら例外。
Math.incrementExact(Integer.MAX_VALUE); // ArithmeticException Math.decrementExact(Integer.MIN_VALUE); // ArithmeticException
実装もシンプル。
public static int incrementExact(int a) { if (a == Integer.MAX_VALUE) { throw new ArithmeticException("integer overflow"); } return a + 1; } public static int decrementExact(int a) { if (a == Integer.MIN_VALUE) { throw new ArithmeticException("integer overflow"); } return a - 1; }
否定
ゼロをプラス側に割り当てるので、int の範囲は -2147483648~2147483647。
Math.negateExact(Integer.MIN_VALUE); // ArithmeticException
-2147483648 の符号変えはオーバーフロー。
public static int negateExact(int a) { if (a == Integer.MIN_VALUE) { throw new ArithmeticException("integer overflow"); } return -a; }
toIntExact
long から int への変換はナロープリミティブ変換となるため、下位32ビットのみが返される。
Integer r = (int) 2_147_483_648L; // -2147483648 Math.toIntExact(2_147_483_648L); // ArithmeticException
toIntExact では例外となる。
実装は、実際にキャストした結果を比較してる。
public static int toIntExact(long value) { if ((int)value != value) { throw new ArithmeticException("integer overflow"); } return (int)value; }
BigInteger ナロープリミティブ変換
BigInteger の intValue() などもナロープリミティブ変換となるため、下位32ビットのみが返される。
BigInteger bigInteger = new BigInteger("..."); bigInteger.byteValueExact(); bigInteger.shortValueExact(); bigInteger.intValueExact(); bigInteger.longValueExact();
同じように ArithmeticException をスローする Exact 系メソッドが追加されている。