Javaは整数アンダーフローとオーバーフローをどのように処理し、どのようにチェックしますか?


Answers

まあ、プリミティブな整数型のところでは、JavaはOver / Underflowを全く処理しません(浮動小数点の場合と動作が異なる場合、IEEE-754の命令と同様に+/-無限にフラッシュされます)。

2つのintを加算すると、オーバーフローが発生したときは何も表示されません。 オーバーフローをチェックする簡単な方法は、実際に操作を実行して結果がソースタイプの範囲内にあるかどうかを確認するために、次に大きいタイプを使用することです。

public int addWithOverflowCheck(int a, int b) {
    // the cast of a is required, to make the + work with long precision,
    // if we just added (a + b) the addition would use int precision and
    // the result would be cast to long afterwards!
    long result = ((long) a) + b;
    if (result > Integer.MAX_VALUE) {
         throw new RuntimeException("Overflow occured");
    } else if (result < Integer.MIN_VALUE) {
         throw new RuntimeException("Underflow occured");
    }
    // at this point we can safely cast back to int, we checked before
    // that the value will be withing int's limits
    return (int) result;
}

スロー句の代わりに行うことは、アプリケーションの要件(スロー、最小/最大にフラッシュ、または何でもログする)に依存します。 長い操作でオーバーフローを検出したい場合は、プリミティブで不運になります。代わりにBigIntegerを使用してください。

Edit(2014-05-21):この質問はかなり頻繁に参照されるようで、私は同じ問題を自分自身で解決しなければならなかったので、CPUがVフラグを計算するのと同じ方法でオーバーフロー条件を非常に簡単に評価できます。

基本的には、両方のオペランドの符号とその結果を含むブール式です。

/**
 * Add two int's with overflow detection (r = s + d)
 */
public static int add(final int s, final int d) throws ArithmeticException {
    int r = s + d;
    if (((s & d & ~r) | (~s & ~d & r)) < 0)
        throw new ArithmeticException("int overflow add(" + s + ", " + d + ")");    
    return r;
}

javaでは、32ビット全体に式を(ifで)適用し、<0を使用して結果をチェックする方が簡単です(これは符号ビットを効果的にテストします)。 この原則は、 すべての整数プリミティブ型に対して全く同じように機能し、上記のメソッドの宣言をすべてlongに変更することで、長い間機能します。

より小さい型の場合は、暗黙的にintに変換されるため(詳細については、ビット単位の演算についてはJLSを参照)、<0をチェックするのではなく、チェックで明示的に符号ビットをマスクする必要があります(ショートオペランドの場合は0x8000、バイトオペランドの場合は0x80、パラメータ宣言が適切に行われます)。

/**
 * Subtract two short's with overflow detection (r = d - s)
 */
public static short sub(final short d, final short s) throws ArithmeticException {
    int r = d - s;
    if ((((~s & d & ~r) | (s & ~d & r)) & 0x8000) != 0)
        throw new ArithmeticException("short overflow sub(" + s + ", " + d + ")");
    return (short) r;
}

(上記の例では、 減算オーバーフロー検出の式の必要性を使用していることに注意してください)

だから、どのように/なぜこれらのブール式が機能するのですか? まず、いくつかの論理的な考え方は、両方の引数の符号が同じである場合にのみオーバーフローが発生する可能性があることを示します。 1つの引数が負で1つが正の場合、(加算の)結果はゼロに近いか、極端な場合には1つの引数がゼロでなければならないため、他の引数と同じです。 引数自体オーバーフロー条件を作成できないため、その合計でオーバーフローも作成できません。

したがって、両方の引数に同じ符号があるとどうなりますか? 両方の型が正の場合を見てみましょう:型MAX_VALUEより大きな合計を作成する2つの引数を追加すると常に負の値が得られるため、arg1 + arg2> MAX_VALUEの場合にオーバーフローが発生します。 結果として得られる最大値はMAX_VALUE + MAX_VALUE(極端な場合は両方の引数がMAX_VALUE)になります。 2つの正の値を加算した結果得られるすべての値のビット表現を見ると、オーバーフローしたもの(128から254)のすべてがビット7のセットを持つことがわかります。一方、127 + 127 = 254を意味するバイトオーバーフローしないもの(0〜127)には、ビット7(最上位、符号)がクリアされます。 式の最初の(右の)部分が正確に何をチェックするか:

if (((s & d & ~r) | (~s & ~d & r)) < 0)

両方のオペランド(s、d)が正で結果(r)が負の場合のみ (〜s&〜d&r)が真となります。式はすべての32ビットで動作しますが、は、最上位(符号)ビットであり、<0>と照合される。

両方の引数が負の場合、それらの合計は引数のいずれよりもゼロに近づくことはありません 。合計マイナスの無限大に近づく必要あります。 我々が生成することができる最も極端な値はMIN_VALUE + MIN_VALUEであり、範囲内の値(-1〜-128)に対して符号ビットが設定され、オーバーフローする可能性のある値(-129〜-256 )は、符号ビットをクリアしています。 結果の符号は再びオーバーフロー状態を示します。 左半分(s&d&〜r)は、両方の引数(s、d)が負の場合と正の結果の場合を調べます。 論理は正の場合とほぼ同じです。 2つの負の値を加算した結果として生じるすべてのビットパターンは、アンダーフローが発生した場合にのみ符号ビットをクリアします。

Question

Javaは整数アンダーフローとオーバーフローをどのように処理しますか?

それを先導して、これが起こっていることをどのようにチェック/テストしますか?




static final int safeAdd(int left, int right)
                 throws ArithmeticException {
  if (right > 0 ? left > Integer.MAX_VALUE - right
                : left < Integer.MIN_VALUE - right) {
    throw new ArithmeticException("Integer overflow");
  }
  return left + right;
}

static final int safeSubtract(int left, int right)
                 throws ArithmeticException {
  if (right > 0 ? left < Integer.MIN_VALUE + right
                : left > Integer.MAX_VALUE + right) {
    throw new ArithmeticException("Integer overflow");
  }
  return left - right;
}

static final int safeMultiply(int left, int right)
                 throws ArithmeticException {
  if (right > 0 ? left > Integer.MAX_VALUE/right
                  || left < Integer.MIN_VALUE/right
                : (right < -1 ? left > Integer.MIN_VALUE/right
                                || left < Integer.MAX_VALUE/right
                              : right == -1
                                && left == Integer.MIN_VALUE) ) {
    throw new ArithmeticException("Integer overflow");
  }
  return left * right;
}

static final int safeDivide(int left, int right)
                 throws ArithmeticException {
  if ((left == Integer.MIN_VALUE) && (right == -1)) {
    throw new ArithmeticException("Integer overflow");
  }
  return left / right;
}

static final int safeNegate(int a) throws ArithmeticException {
  if (a == Integer.MIN_VALUE) {
    throw new ArithmeticException("Integer overflow");
  }
  return -a;
}
static final int safeAbs(int a) throws ArithmeticException {
  if (a == Integer.MIN_VALUE) {
    throw new ArithmeticException("Integer overflow");
  }
  return Math.abs(a);
}



デフォルトでは、Javaのintおよびlong演算は、オーバーフローおよびアンダーフロー時に自動的に折り返されます。 (他の整数型に対する整数演算は、まずJLS 4.2.2に従って、オペランドをintまたはlongに昇格させることによって実行されます)。

Java 8以降、 java.lang.Mathは、名前付き操作を実行するintおよびlong引数の両方に対してaddExactsubtractExactmultiplyExactincrementExactdecrementExact 、およびnegateExact静的メソッドを提供し、オーバーフロー時にArithmeticExceptionをスローします。 (divideExactメソッドはありません。あなたは1つの特別なケース( MIN_VALUE / -1 )を自分で確認する必要があります。

Java 8以降、java.lang.MathはtoIntExactを提供してlongをintにキャストし、longの値がintに収まらない場合はArithmeticExceptionをスローします。 これは、チェックされていない長い数値を使ってintの総和を計算し、最後にtoIntExactを使ってintにキャストするのに便利です(しかし、合計がオーバーフローしないように注意してください)。

それでも古いバージョンのJavaを使用している場合、Google Guavaはチェック、加算、減算、乗算、べき乗(オーバーフローを起こす)のためにIntMathとLongMathの静的メソッドを提供します。 これらのクラスは、オーバーフロー時にMAX_VALUEを返す階乗と二項係数を計算するメソッドも提供します(これはチェックするのが簡単ではありません)。 Guavaの初期のユーティリティクラスSignedBytesUnsignedBytesShorts 、およびInts 、オーバーフロー時にMIN_VALUEまたはMAX_VALUEを返すsaturatingCastメソッドだけでなく 、より大きい型を絞り込むためのcheckedCastメソッドを提供します(IllegalArgumentExceptionをArithmeticExceptionではなくオーバーフローで投げる)。




私はこれがうまくいくはずだと思います。

static boolean addWillOverFlow(int a, int b) {
    return (Integer.signum(a) == Integer.signum(b)) && 
            (Integer.signum(a) != Integer.signum(a+b)); 
}



それは何もしません - アンダーフロー/オーバーフローが起こります。

オーバーフローした計算の結果である「-1」は、他の情報から生じた「-1」と変わらない。 だから、あなたは、ある状態を通って、あるいは値がオーバーフローしているかどうかを調べることによって、それを知ることはできません。

しかし、オーバーフローを避けるために計算を精査することができます。問題が起きた場合や、発生するタイミングを少なくとも知ることができます。 あなたの状況はどうですか?




整数オーバーフロー/アンダーフローをチェックする安全な算術演算を提供するライブラリがあります。 たとえば、GuavaのIntMath.checkedAdd(int a、int b)は、オーバーフローしていない場合はbb合計を返し、符号付きint算術でa + bがオーバーフローした場合はArithmeticExceptionをスローします。




Links