読者です 読者をやめる 読者になる 読者になる

Java8 で追加された Exact 系メソッド


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 系メソッドが追加されている。