Javaで2つの数値を乗算するとオーバーフローが発生するかどうかを確認するにはどうすればよいですか?


101

2つの数値を乗算するとオーバーフローが発生するという特殊なケースを処理したいと思います。コードは次のようになります。

int a = 20;
long b = 30;

// if a or b are big enough, this result will silently overflow
long c = a * b;

これは簡易バージョンです。実際のプログラムabは、実行時に他の場所から供給されます。私が達成したいのは次のようなものです:

long c;
if (a * b will overflow) {
    c = Long.MAX_VALUE;
} else {
    c = a * b;
}

これを最もよくコーディングすることをどのように提案しますか?

更新:ab私のシナリオでは非負常にあります。


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

回答:


92

Java 8にはMath.multiplyExactMath.addExactintやlongなどのがあります。これらArithmeticExceptionはオーバーフロー時に未チェックをスローします。


59

aとのb両方が正の場合は、次のように使用できます。

if (a != 0 && b > Long.MAX_VALUE / a) {
    // Overflow
}

正の数と負の数の両方を処理する必要がある場合は、さらに複雑になります。

long maximum = Long.signum(a) == Long.signum(b) ? Long.MAX_VALUE : Long.MIN_VALUE;

if (a != 0 && (b > 0 && b > maximum / a ||
               b < 0 && b < maximum / a))
{
    // Overflow
}

オーバーフローが-10または+10で発生するふりをして、これを確認するために作成した小さなテーブルを次に示します。

a =  5   b =  2     2 >  10 /  5
a =  2   b =  5     5 >  10 /  2
a = -5   b =  2     2 > -10 / -5
a = -2   b =  5     5 > -10 / -2
a =  5   b = -2    -2 < -10 /  5
a =  2   b = -5    -5 < -10 /  2
a = -5   b = -2    -2 <  10 / -5
a = -2   b = -5    -5 <  10 / -2

私のシナリオでは、aとbは常に非負であることを述べておかなければなりません。これにより、このアプローチが多少簡略化されます。
スティーブマクロード

3
これは失敗する可能性があると思います:a = -1およびb =10。最大/ a式はInteger.MIN_VALUEを生成し、何も存在しないときにオーバーフローを検出します
Kyle

これは本当にいいです。不思議に思う人にとって、これが機能する理由は、integerの場合nn > xと同じだからn > floor(x)です。正の整数の場合、除算は暗黙のフロアを行います。(負の数の場合は切り上げられます)
Thomas Ahle

a = -1b = 10問題に対処するには、以下の私の回答を参照してください。
Jim Pivarski、2015年

17

長いオーバーフロー/アンダーフローをチェックする安全な算術演算を提供するJavaライブラリがあります。たとえば、GuavaのLongMath.checkedMultiply(long a、long b)aand の積を返します。オーバーフローしない場合は、符号付き演算でオーバーフローした場合にbスローされます。ArithmeticExceptiona * blong


4
これが最良の答えです。Javaの機械演算を本当に理解している人が実装し、多くの人がテストしたライブラリを使用してください。独自のコードを記述したり、他の回答に投稿されている未テストのテストされていないコードを使用したりしないでください。
リッチ

@Enerccio-あなたのコメントを理解できません。Guavaがすべてのシステムで動作するとは限らないと言っていますか?Javaが動作するすべての場所で動作することを保証します。コードを再利用することは一般的に悪い考えだと言っていますか?そうだと思います。
リッチ

2
@リッチ1つの関数を使用できるように巨大なライブラリを含めることは悪い考えです。
Enerccio

どうして?たとえばビジネス向けの大きなアプリケーションを作成している場合、クラスパス上の追加のJARは害を及ぼすことはなく、Guavaには非常に便利なコードがたくさん含まれています。慎重にテストされたコードを再利用する方が、自分で同じバージョンを作成するよりもはるかに優れています(私が推奨するのはこれだと思いますか?)。追加のJARが非常に高価になる環境(組み込みJavaですか?)で作成している場合は、おそらくこのクラスだけをGuavaから抽出する必要があります。StackOverflowからテストされていない回答をコピーすることは、Guavaの慎重にテストされたコードをコピーすることよりも優れていますか?
リッチ

例外をスローすることは、条件付きのif-thenが処理できるものに対して少しやりすぎではありませんか?
victtim

6

代わりにjava.math.BigIntegerを使用して、結果のサイズを確認できます(コードはテストしていません)。

BigInteger bigC = BigInteger.valueOf(a) * multiply(BigInteger.valueOf(b));
if(bigC.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0) {
  c = Long.MAX_VALUE;
} else {
  c = bigC.longValue()
}

7
私はこの解決策がかなり遅いと
感じてい

ただし、おそらくそれが最善の方法です。私はこれが数値アプリケーションであると想定しました。そのため、私はオフハンドでそれを推奨しませんでしたが、これはおそらくこの問題を解決するためのおそらく最良の方法です。
Stefan Kendall、

2
BigIntegerで '>'演算子を使用できるかどうかはわかりません。compareToメソッドを使用する必要があります。
ピエール

compareToに変更されました。速度が重要かどうかは、コードが使用される状況によって異なります。
Ulf Lindback 2009年

5

結果のサイズを確認するには、対数を使用してください。


どういう意味ceil(log(a)) + ceil(log(b)) > log(Long.MAX)ですか?
Thomas Jung、

1
確認しました。小さな値の場合、BigIntegerより20%高速で、MAXに近い値の場合、ほとんど同じです(5%高速)。Yossarianのコードは最も高速です(BigIntegerより95%および75%高速)。
Thomas Jung、

場合によっては失敗するのではないかと思います。
トム・ホーティン-2009年

整数ログは効果的に先行ゼロの数を数えるだけであり、いくつかの一般的なケースを最適化できます(たとえば、((a | b)&0xffffffff00000000L)== 0)あなたが安全であることがわかっていることを思い出してください)。一方、最も一般的なケースで最適化を30/40クロックサイクルまで下げることができない場合を除いて、John Kugelmanの方法はおそらくより良いパフォーマンスを発揮します(整数の除算は約2ビット/クロックサイクルです)。
Neil Coffey、

PS Sorr、私はANDマスクに追加のビットセット(0xffffffff80000000L)が必要だと思います-少し遅いですが、アイデアはわかります...
Neil Coffey

4

Javaにはint.MaxValueのようなものがありますか?はいの場合は、

if (b != 0 && Math.abs(a) > Math.abs(Long.MAX_VALUE / b))
{
 // it will overflow
}

編集:問題のLong.MAX_VALUEを見た


私は反対投票はしませんでしたが、のMath.Abs(a)場合aは機能しませんLong.MIN_VALUE
John Kugelman、

@John-aとbは> 0です。Yossarianのアプローチ(b!= 0 && a> Long.MAX_VALUE / b)が最良だと思います。
Thomas Jung、

@ Thomas、aおよびbは> = 0、つまり負ではありません。
スティーブマクロード

2
そうですが、その場合、Absは必要ありません。負の数が許可されている場合、これは少なくとも1つのエッジケースで失敗します。私が言っているのはそれだけです。
John Kugelman、

Javaでは、Math.Abs​​ではなくMath.absを使用する必要があります(C#の人ですか?)
dfa 2009年

4

これが私が考えることができる最も簡単な方法です

int a = 20;
long b = 30;
long c = a * b;

if(c / b == a) {
   // Everything fine.....no overflow
} else {
   // Overflow case, because in case of overflow "c/b" can't equal "a"
}

3

ジュルビーから盗まれた

    long result = a * b;
    if (a != 0 && result / a != b) {
       // overflow
    }

更新:このコードは短く、うまく機能します。ただし、a = -1、b = Long.MIN_VALUEの場合は失敗します。

可能な拡張機能の1つ:

long result = a * b;
if( (Math.signum(a) * Math.signum(b) != Math.signum(result)) || 
    (a != 0L && result / a != b)) {
    // overflow
}

これは、除算なしでいくつかのオーバーフローをキャッチすることに注意してください。


Math.signumの代わりにLong.signumを使用できます
aditsuはSEがEVILであるため終了しました

3

指摘したように、Java 8には、オーバーフロー時に例外をスローするMath.xxxExactメソッドがあります。

プロジェクトにJava 8を使用していない場合でも、かなりコンパクトな実装を「借りる」ことができます。

JDKソースコードリポジトリ内のこれらの実装へのリンクをいくつか示します。これらが有効であるかどうかは保証されませんが、いずれの場合もJDKソースをダウンロードして、java.lang.Mathクラス内でどのように機能するかを確認できます。

Math.multiplyExact(long, long) http://hg.openjdk.java.net/jdk/jdk11/file/1ddf9a99e4ad/src/java.base/share/classes/java/lang/Math.java#l925

Math.addExact(long, long) http://hg.openjdk.java.net/jdk/jdk11/file/1ddf9a99e4ad/src/java.base/share/classes/java/lang/Math.java#l830

などなど

更新:サードパーティのWebサイトへの無効なリンクをOpen JDKのMercurialリポジトリへのリンクに切り替えました。


2

なぜ誰もが次のような解決策を見ているのかわかりません:

if (Long.MAX_VALUE/a > b) {
     // overflows
} 

2つの数値のうち大きい方を選択します。


2
a大きいか小さいかは重要ではないと思いますか?
Thomas

2

John Kugelmanの回答を、直接編集することで置き換えずに構築したいと思います。これは、2の補数の整数には当てはまらないの対称性のためMIN_VALUE = -10、彼のテストケース(、MAX_VALUE = 10)で機能しMIN_VALUE == -MAX_VALUEます。実際には、MIN_VALUE == -MAX_VALUE - 1

scala> (java.lang.Integer.MIN_VALUE, java.lang.Integer.MAX_VALUE)
res0: (Int, Int) = (-2147483648,2147483647)

scala> (java.lang.Long.MIN_VALUE, java.lang.Long.MAX_VALUE)
res1: (Long, Long) = (-9223372036854775808,9223372036854775807)

trueに適用された場合MIN_VALUEMAX_VALUE、ジョン・Kugelmanの答えは、オーバーフローケース生成a == -1b ==(最初のカイルが提起した時点)何かを。これを修正する方法は次のとおりです。

long maximum = Long.signum(a) == Long.signum(b) ? Long.MAX_VALUE : Long.MIN_VALUE;

if ((a == -1 && b == Long.MIN_VALUE) ||
    (a != -1 && a != 0 && ((b > 0 && b > maximum / a) ||
                           (b < 0 && b < maximum / a))))
{
    // Overflow
}

それは一般的なソリューションではありません MIN_VALUEMAX_VALUE、それはJavaのために一般的であるLongIntegerの任意の値ab


このソリューションMIN_VALUE = -MAX_VALUE - 1は、他のケース(テストケースの例を含む)ではなく、の場合にのみ機能するため、必要以上に複雑になると思いました。私はたくさん変えなければならないでしょう。
Jim Pivarski、2015年

1
元のポスターの必要性を超えた理由で; 私のような人々がこのページを見つけたのは、もっと一般的なケース(厳密にはJava 8ではなく負の数を扱う)の解決策が必要だったからです。実際、このソリューションには純粋な算術演算とロジック以外の関数は含まれていないため、Cまたは他の言語にも使用できます。
Jim Pivarski、2015年

1

多分:

if(b!= 0 && a * b / b != a) //overflow

この「解決策」については不明です。

編集:b!= 0を追加しました。

反対票を投じる前に:a * b / bは最適化されません。これはコンパイラのバグです。オーバーフローバグをマスクできるケースはまだ表示されません。


オーバーフローが完全なループを引き起こした場合にも失敗します。
Stefan Kendall、

意味の例はありますか?
Thomas Jung、

少しテストを書いただけです。BigIntegerを使用すると、この除算アプローチを使用する場合よりも6倍遅くなります。ですから、コーナーケースの追加のチェックは、パフォーマンス面でそれだけの価値があると思います。
マラー2009年

Javaコンパイラについてはあまり知りませんが、などの式a * b / ba他の多くのコンテキストでのみ最適化される可能性があります。
SingleNegationElimination 2009年

TokenMacGuy-オーバーフローの危険がある場合、そのように最適化することはできません。
トム・ホーティン-2009年

1

多分これはあなたを助けるでしょう:

/**
 * @throws ArithmeticException on integer overflow
 */
static long multiply(long a, long b) {
    double c = (double) a * b;
    long d = a * b;

    if ((long) c != d) {
        throw new ArithmeticException("int overflow");
    } else {
        return d;
    }
}

オペランドの1つがであるため、ヘルプはありませんlong
トム・ホーティン-2009年

1
これもテストしましたか?aおよびbのオーバーフローしていない大きな値の場合、これは乗算の二重バージョンでの丸めエラーのために失敗します(たとえば、123456789123Lおよび74709314Lを試してください)。機械演算を理解していない場合、この種の正確な質問への回答を推測することは、回答しないことよりも悪いことです。
リッチ

-1

c / c ++(長い*長い):

const int64_ w = (int64_) a * (int64_) b;    
if ((long) (w >> sizeof(long) * 8) != (long) w >> (sizeof(long) * 8 - 1))
    // overflow

java(int * int、申し訳ありませんが、Javaでint64が見つかりませんでした):

const long w = (long) a * (long) b;    
int bits = 32; // int is 32bits in java    
if ( (int) (w >> bits) != (int) (w >> (bits - 1))) {
   // overflow
}

1.結果を大きな型で保存します(int * intは結果をlongに、long * longはint64に配置します)

2.cmp結果>>ビットと結果>>(ビット-1)

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