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


226

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

それから先に、これが発生していることをどのように確認/テストしますか?


28
C#で行われいるように、JavaがCPUのオーバーフローフラグへの間接アクセスを提供しないのは残念です。
Drew Noakes 2012

@DrewNoakesそして、C#がchecked私の知る限りデフォルトになっていないのは残念です。私はそれがあまり使われていないと思います、そしてタイピングchecked { code; }はメソッドを呼び出すのと同じくらい多くの仕事です。
Maarten Bodewes

2
@MaartenBodewes、アセンブリのコンパイル時にデフォルトとして設定できます。csc /checked ...または、Visual Studioのプロジェクトのプロパティペインでプロパティを設定します。
Drew Noakes

@DrewNoakes OK、面白い。それはコード外の設定ですが少し奇妙です。一般的に、そのような設定(アサーションを除く)に関係なく、プログラムの動作は同じにしたいと思います。
Maarten Bodewes 2016年

@MaartenBodewes、私はその理由はチェックのために重要なperfオーバーヘッドがあることだと思います。したがって、他の多くの種類のアサーションと同様に、デバッグビルドで有効にしてから、リリースビルドで無効にすることができます。
Drew Noakes 2016年

回答:


217

オーバーフローすると、最小値に戻り、そこから続行します。アンダーフローすると、最大値に戻り、そこから継続します。

次のようにして、事前に確認できます。

public static boolean willAdditionOverflow(int left, int right) {
    if (right < 0 && right != Integer.MIN_VALUE) {
        return willSubtractionOverflow(left, -right);
    } else {
        return (~(left ^ right) & (left ^ (left + right))) < 0;
    }
}

public static boolean willSubtractionOverflow(int left, int right) {
    if (right < 0) {
        return willAdditionOverflow(left, -right);
    } else {
        return ((left ^ right) & (left ^ (left - right))) < 0;
    }
}

(あなたは置き換えることができますintによってlongも同じチェックを実行するためにlong

あなたはこれ以上頻繁に発生することがあり、その後、例えば、より大きな値を格納することができ、データ型やオブジェクトを使用することを検討していることを思うならlong、多分かjava.math.BigInteger。最後のものはオーバーフローしません。実際には、利用可能なJVMメモリが限界です。


すでにJava8を使用している場合は、オーバーフローをスローする新しいメソッドMath#addExact()Math#subtractExact()メソッドを使用できますArithmeticException

public static boolean willAdditionOverflow(int left, int right) {
    try {
        Math.addExact(left, right);
        return false;
    } catch (ArithmeticException e) {
        return true;
    }
}

public static boolean willSubtractionOverflow(int left, int right) {
    try {
        Math.subtractExact(left, right);
        return false;
    } catch (ArithmeticException e) {
        return true;
    }
}

ソースコードはここここにそれぞれあります。

もちろん、booleanユーティリティメソッドで非表示にする代わりに、すぐに使用することもできます。


13
@ dhblah、Javaがintに許可する最大値と最小値は+100, -100それぞれであるとしましょう。Java整数に1を追加すると、プロセスはオーバーフローして次のようになります。98, 99, 100, -100, -99, -98, ...。それはもっと理にかなっていますか?
オースティンA

6
コードをすぐに使用するのではなく、ユーティリティメソッドを使用することをお勧めします。ユーティリティメソッドは組み込みであり、マシン固有のコードに置き換えられます。簡単なテストでは、Math.addExactがコピーされたメソッド(Java 1.8.0_40)より30%速いことがわかりました。
TilmannZ

1
@ErikE Math#addExactは、通常javadocsを記述するときに使用される構文です-通常はに変換されMath.addExactますが、他の形式がそのまま残ることもあります
Pokechu22

1
If it underflows, it goes back to the maximum value and continues from there.-アンダーフローと負のオーバーフローを混同しているようです。整数のアンダーフローは常に発生します(結果が小数の場合)。
身廊

1
en.wikipedia.org/wiki/Arithmetic_underflowは、計算の結果がコンピューターのCPUのメモリで実際に表すことができるよりも小さな絶対値の数であるコンピュータープログラムの条件であると述べています。したがって、アンダーフローはJava整数には適用されません。@BalusC
Jingguo八尾

66

まあ、プリミティブ整数型に関する限り、JavaはOver / Underflowをまったく処理しません(floatとdoubleの場合は動作が異なり、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;
}

throw句の代わりに何をするかは、アプリケーションの要件に依存します(throw、min / maxにフラッシュするか、単に何でもログに記録します)。長時間の操作でオーバーフローを検出したい場合は、プリミティブではうまくいかないため、代わりにBigIntegerを使用してください。


編集(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では、式(if内)を32ビット全体に適用し、<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;
}

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


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

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

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

(〜s&〜d&r)は、両方のオペランド(s、d)が正で結果(r)が負の場合のみ trueになります(式は32ビットすべてで機能しますが、関心のある唯一のビット最上位の(符号)ビットであり、<0によってチェックされます)。

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



1
これは機能しますが、パフォーマンスに悪影響を与えると想定しています。
chessofnerd

33

デフォルトでは、Javaのintとlong mathは、オーバーフローとアンダーフローで静かにラップアラウンドします。(他の整数型に対する整数演算は、JLS 4.2.2に従って、最初にオペランドをintまたはlongに昇格させることによって実行されます。)

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

Java 8以降、java.lang.Math toIntExactはlongをintにキャストする機能も提供し、longの値がintに適合しない場合はArithmeticExceptionをスローします。これは、たとえば、チェックされていない長い数学を使用してintの合計を計算toIntExactし、最後にintにキャストするために使用できます(ただし、合計がオーバーフローしないように注意してください)。

まだ古いバージョンのJavaを使用している場合、Google Guavaは、チェックされた加算、減算、乗算、および累乗(オーバーフローでスロー)のためのIntMathおよびLongMath静的メソッドを提供します。これらのクラスはMAX_VALUE、オーバーフローで返される階乗と二項係数を計算するメソッドも提供します(これはチェックするのに不便です)。グアバのプリミティブユーティリティクラス、SignedBytesUnsignedBytesShorts及びはInts、提供するcheckedCastより大きなタイプ(下/オーバーフローに、IllegalArgumentExceptionを投げる、狭くする方法ではないはArithmeticException)、ならびにsaturatingCast方法リターンそのMIN_VALUE又はMAX_VALUEオーバーフローにします。


32

Javaは、intまたはlongプリミティブ型の整数オーバーフローについては何もせず、正および負の整数によるオーバーフローを無視します。

この回答では、最初に整数オーバーフローについて説明し、式の評価で中間値を使用した場合でも発生する例を示し、次に整数オーバーフローを防止および検出するための詳細な手法を提供するリソースへのリンクを示します。

予期しないまたは検出されないオーバーフローを引き起こす整数演算および式は、一般的なプログラミングエラーです。予期しない、または検出されない整数オーバーフローも、特に配列、スタック、リストオブジェクトに影響を与えるため、悪用可能な既知のセキュリティ問題です。

オーバーフローは、正または負の方向に発生する可能性があり、正または負の値は、問題のプリミティブタイプの最大値または最小値を超えます。オーバーフローは、式または演算の評価中に中間値で発生し、最終値が範囲内であると予想される式または演算の結果に影響を与える可能性があります。

負のオーバーフローは、誤ってアンダーフローと呼ばれることがあります。アンダーフローは、表現が許可するよりも値がゼロに近い場合に発生します。アンダーフローは整数演算で発生し、予期されています。整数のアンダーフローは、整数の評価が-1から0または0から1の間になる場合に発生します。小数の結果は0に切り捨てられます。これは正常であり、整数演算で予期され、エラーとは見なされません。ただし、コードが例外をスローする可能性があります。1つの例は、整数アンダーフローの結果が式の除数として使用されている場合の「ArithmeticException:/ by zero」例外です。

次のコードを検討してください。

int bigValue = Integer.MAX_VALUE;
int x = bigValue * 2 / 5;
int y = bigValue / x;

その結果、xには0が割り当てられ、それに続くbigValue / xの評価では、yに値2が割り当てられる代わりに、例外「ArithmeticException:/ by zero」(つまり、ゼロで除算)がスローされます。

xの予想結果は858,993,458であり、これは最大のint値である2,147,483,647未満です。ただし、Integer.MAX_Value * 2の評価からの中間結果は、4,294,967,294となり、これは最大のint値を超え、2の補数整数表現によれば-2です。-2 / 5の後続の評価は0に評価され、xに割り当てられます。

xを計算するための式を、評価するときに乗算する前に除算する式に次のコードを再配置します。

int bigValue = Integer.MAX_VALUE;
int x = bigValue / 5 * 2;
int y = bigValue / x;

その結果、xには858,993,458が割り当てられ、yには2が割り当てられますが、これは予期されていることです。

bigValue / 5の中間結果は429,496,729であり、intの最大値を超えません。429,496,729 * 2の後続の評価は、intの最大値を超えず、期待される結果がxに割り当てられます。yの評価はゼロで除算されません。xとyの評価は期待どおりに機能します。

Java整数値は、2の補数符号付き整数表現として格納され、それに従って動作します。結果の値が最大または最小の整数値よりも大きいまたは小さい場合、代わりに2の補数の整数値が生成されます。2の補数の動作を使用するように明確に設計されていない状況、つまり最も一般的な整数演算の状況では、結果の2の補数の値により、上記の例で示したようにプログラミングロジックまたは計算エラーが発生します。Wikipediaの優れた記事では、2の補数の2進整数についてここで説明しています。2の補数-ウィキペディア

意図しない整数オーバーフローを回避するための手法があります。Techinquesは、事前条件テスト、アップキャスト、およびBigIntegerを使用するものとして分類できます。

事前条件テストでは、算術演算または式に入る値を調べて、それらの値でオーバーフローが発生しないことを確認します。プログラミングと設計では、入力値がオーバーフローを引き起こさないことを確認するテストを作成し、オーバーフローを引き起こす入力値が発生した場合の対処法を決定する必要があります。

アップキャスティングでは、より大きなプリミティブ型を使用して算術演算または式を実行し、結果の値が整数の最大値または最小値を超えているかどうかを判断します。アップキャストを使用しても、演算または式の値または中間値がアップキャストタイプの最大値または最小値を超えてオーバーフローが発生する可能性があり、これも検出されず、予期しない望ましくない結果が発生します。分析または事前条件により、アップキャストなしでの防止が不可能または実用的でない場合、アップキャストでオーバーフローを防止できる可能性があります。問題の整数がすでに長いプリミティブ型である場合、Javaのプリミティブ型ではアップキャストできません。

BigInteger手法は、BigIntegerを使用するライブラリメソッドを使用した算術演算または式にBigIntegerを使用することで構成されます。BigIntegerはオーバーフローしません。必要に応じて、使用可能なすべてのメモリを使用します。その算術メソッドは通常、整数演算よりもわずかに効率が悪いだけです。BigIntegerを使用した結果が整数の最大値または最小値を超える可能性がありますが、結果に至るまでの算術演算でオーバーフローは発生しません。BigIntegerの結果が、intやlongなどの目的のプリミティブな結果タイプの最大値または最小値を超えている場合は、プログラミングと設計でどうするかを決定する必要があります。

カーネギーメロンソフトウェアエンジニアリングインスティテュートのCERTプログラムとOracleは、安全なJavaプログラミングのための一連の標準を作成しました。標準には、整数オーバーフローを防止および検出するための技術が含まれています。この標準は、無料でアクセスできるオンラインリソースとしてここに公開されています。Java用のCERT Oracle Secure Coding Standard

整数オーバーフローを防止または検出するためのコーディング手法の実用的な例を説明し、含む標準のセクションは、NUM00-Jです。整数オーバーフローを検出または防止する

Java用のCERT Oracle Secure Coding StandardのブックフォームとPDFフォームも利用できます。


それは明らかにアンダーフローが何であるかを述べて(受け入れられた答えはありません)ともオーバーフロー/アンダーフローに対処するためのテクニックを示しています、これはここで最高の答えです
身廊

12

自分でこの問題にちょっと遭遇したので、これが私の解決策です(乗算と加算の両方):

static boolean wouldOverflowOccurwhenMultiplying(int a, int b) {
    // If either a or b are Integer.MIN_VALUE, then multiplying by anything other than 0 or 1 will result in overflow
    if (a == 0 || b == 0) {
        return false;
    } else if (a > 0 && b > 0) { // both positive, non zero
        return a > Integer.MAX_VALUE / b;
    } else if (b < 0 && a < 0) { // both negative, non zero
        return a < Integer.MAX_VALUE / b;
    } else { // exactly one of a,b is negative and one is positive, neither are zero
        if (b > 0) { // this last if statements protects against Integer.MIN_VALUE / -1, which in itself causes overflow.
            return a < Integer.MIN_VALUE / b;
        } else { // a > 0
            return b < Integer.MIN_VALUE / a;
        }
    }
}

boolean wouldOverflowOccurWhenAdding(int a, int b) {
    if (a > 0 && b > 0) {
        return a > Integer.MAX_VALUE - b;
    } else if (a < 0 && b < 0) {
        return a < Integer.MIN_VALUE - b;
    }
    return false;
}

間違っている場合、または簡略化できる場合は、自由に修正してください。乗算法を使用していくつかのテストを行いましたが、ほとんどがエッジケースですが、それでもまだ間違っている可能性があります。


除算は乗算に比べて遅くなりがちです。についてはint*int、単純にキャストしlongて結果が収まるかどうかを確認intするのが最速のアプローチだと思います。の場合long*long、オペランドを正に正規化すると、それぞれを32ビットの上位半分と下位半分に分割し、各半分をlongに昇格し(符号拡張に注意してください!)、次に2つの部分積を計算できます[上半分の1つはゼロ]。
スーパーキャット2014

「long * longの場合、オペランドを正に正規化すると...」と言うとき、Long.MIN_VALUEを正規化するにはどうすればよいでしょうか。
fragorl 2014

これらのメソッドは、実際に計算を実行するに何かがオーバーフローしているかどうかをテストする必要がある場合に興味深いかもしれません。例外が発生したときにキャッチするのではなく、そのような計算に使用されるユーザー入力などをテストするのに適しています。
Maarten Bodewes

8

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


うん、それはあなたがJava 8以上でない限りそれは良い考えです、その場合Mathクラスには同様のコードが含まれています。
Maarten Bodewes

6

包み込みます。

例えば:

public class Test {

    public static void main(String[] args) {
        int i = Integer.MAX_VALUE;
        int j = Integer.MIN_VALUE;

        System.out.println(i+1);
        System.out.println(j-1);
    }
}

プリント

-2147483648
2147483647

上手!そして今、あなたは答えることができます、それを複雑な計算にどのように検出するのですか?
Aubin 2013年

5

私はあなたはこのようなものを使うべきだと思います、それはアップキャストと呼ばれています:

public int multiplyBy2(int x) throws ArithmeticException {
    long result = 2 * (long) x;    
    if (result > Integer.MAX_VALUE || result < Integer.MIN_VALUE){
        throw new ArithmeticException("Integer overflow");
    }
    return (int) result;
}

あなたはここでさらに読むことができます: 整数オーバーフローを検出または防止する

かなり信頼できる情報源です。


3

それは何もしません-アンダーフロー/オーバーフローが発生するだけです。

オーバーフローした計算の結果である「-1」は、他の情報から得られた「-1」と違いはありません。そのため、ステータスによって、または値だけを調べてオーバーフローしているかどうかを判断することはできません。

しかし、オーバーフローを回避するために、計算が賢明であるか、問題が発生した場合、または少なくともいつ発生するかを知ることができます。あなたの状況はどうですか?


それは実際の状況ではなく、私が興味を持ち、考えさせられたものです。ユースケースの例が必要な場合は、次のとおりです。「秒」という独自の内部変数を持つクラスがあります。整数をパラメーターとして受け取り、「秒」を(それぞれ)増分または減分する2つのメソッドがあります。アンダーフロー/オーバーフローが発生していることをどのように単体テストし、それが発生しないようにするにはどうすればよいですか?
KushalP 2010年

1
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);
}

2
これはテストを処理します。ただし、Javaによる整数のアンダーフローとオーバーフローの処理方法については説明していません(説明するテキストを追加してください)。
スペンサーWieczorek 2014年

1

これで大丈夫だと思います。

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.