Javaは整数のアンダーフローとオーバーフローをどのように処理しますか?
それから進んで、これが起こっていることをどのようにチェック/テストしますか?
オーバーフローした場合は、 最小値 に戻り、そこから続行します。アンダーフローすると、 最大値 に戻り、そこから続行します。
次のように事前に確認できます。
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を使用している場合は、オーバーフロー時にArithmeticException
をスローする新しい Math#addExact()
および Math#subtractExact()
メソッドを使用できます。
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;
}
}
ソースコードは、それぞれ here および here にあります。
もちろん、boolean
ユーティリティメソッドで非表示にする代わりに、すぐに使用することもできます。
さて、プリミティブ整数型に関する限り、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;
}
(上記の例ではsubtract overflow detectionの式が必要であることに注意してください)
それでは、これらのブール式はどのように/なぜ動作するのでしょうか?最初に、いくつかの論理的思考は、両方の引数の符号が同じ場合にオーバーフローが発生する可能性があることを明らかにしていますonlyなぜなら、1つの引数が負で、1つの引数が正の場合、(addの)mustの結果はゼロに近くなり、極端な場合、1つの引数は他の引数と同じゼロになります。引数自体はca n'tでオーバーフロー条件を作成するため、それらの合計もオーバーフローを作成できません。
では、両方の引数の符号が同じ場合はどうなりますか?両方とも正の場合を見てみましょう:MAX_VALUE型よりも大きい合計を作成する2つの引数を追加すると、常に負の値が生成されるため、オーバーフローが発生しますif 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)が真になり、if、両方のオペランド(s、d)が正で、結果(r)が負(式は32ビットすべてで機能しますが、関心のあるビットのみが最上位(符号)ビットで、<0でチェックされます)。
両方の引数が負の場合、それらの合計がどの引数よりもゼロに近くなることはありません。合計mustは負の無限大に近くなります。生成できる最も極端な値はMIN_VALUE + MIN_VALUEです。これは(バイトの例でも)範囲内の値(-1〜-128)に対して符号ビットが設定され、オーバーフローする可能性のある値(-129〜-256)を示します。 )符号ビットがクリアされています。したがって、結果の符号は再びオーバーフロー状態を明らかにします。つまり、左半分(s&d&〜r)は、両方の引数(s、d)が負であり、結果が正である場合をチェックします。論理は正の場合とほぼ同等です。 2つの負の値を追加することで発生する可能性のあるすべてのビットパターンでは、符号ビットがクリアされますif if onlyアンダーフローが発生しました。
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の補数の値により、上記の例で示したようにプログラミングロジックまたは計算エラーが発生します。優れたウィキペディアの記事では、2の補数のバイナリ整数について説明しています。 2の補数-ウィキペディア
意図しない整数オーバーフローを回避する手法があります。 Techinquesは、事前条件テスト、アップキャスト、およびBigIntegerを使用するものとして分類できます。
前提条件のテストでは、算術演算または式に入る値を調べて、それらの値でオーバーフローが発生しないことを確認します。プログラミングと設計では、入力値がオーバーフローを引き起こさないことを確認するテストを作成し、オーバーフローを引き起こす入力値が発生した場合の対処方法を決定する必要があります。
アップキャストでは、より大きなプリミティブ型を使用して算術演算または式を実行し、結果の値が整数の最大値または最小値を超えているかどうかを判断します。アップキャストでも、操作または式の値または中間値がアップキャストタイプの最大値または最小値を超えてオーバーフローを引き起こす可能性がありますが、オーバーフローも検出されず、予期しない望ましくない結果を引き起こす可能性があります。分析または前提条件により、アップキャストなしの防止が不可能または実用的でない場合、アップキャストによるオーバーフローを防止できる場合があります。問題の整数がすでに長いプリミティブ型である場合、Javaのプリミティブ型ではアップキャストはできません。
BigInteger手法は、BigIntegerを使用するライブラリメソッドを使用して、算術演算または式にBigIntegerを使用することで構成されます。 BigIntegerはオーバーフローしません。必要に応じて、使用可能なすべてのメモリを使用します。通常、その算術メソッドは整数演算よりもわずかに効率が劣ります。 BigIntegerを使用した結果が整数の最大値または最小値を超える可能性はありますが、結果に至る演算でオーバーフローは発生しません。プログラミングおよび設計では、BigIntegerの結果が目的のプリミティブ結果タイプの最大値または最小値(intまたはlongなど)を超えている場合の対処方法を決定する必要があります。
カーネギーメロンソフトウェアエンジニアリングインスティテュートのCERTプログラムとOracleは、安全なJavaプログラミングのための一連の標準を作成しました。標準には、整数オーバーフローを防止および検出するための技術が含まれています。標準は、ここから自由にアクセスできるオンラインリソースとして公開されています。 CERT Oracle Secure Coding Standard for Java
整数オーバーフローを防止または検出するためのコーディング手法の実用的な例を説明し、含む標準のセクションは次のとおりです。 NUM00-J。整数オーバーフローの検出または防止
書籍フォームとPDFフォームのCERT Oracle Secure Coding Standard for Javaも利用できます。
デフォルトでは、Javaのintおよびlong mathは、オーバーフローとアンダーフローで静かにラップアラウンドします。 (他の整数型の整数演算は、最初にオペランドをintまたはlongに昇格することにより、 JLS 4.2.2 に従って実行されます。)
Java 8以降、Java.lang.Math
は addExact
、 subtractExact
、 multiplyExact
、 incrementExact
、-を提供します decrementExact
および negateExact
名前付き操作を実行するint引数とlong引数の両方の静的メソッドで、オーバーフロー時に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のプリミティブユーティリティクラスSignedBytes
、UnsignedBytes
、Shorts
、およびInts
は、より大きな型を絞り込むためのcheckedCast
メソッドを提供します(アンダーフロー/オーバーフローでIllegalArgumentExceptionをスロー、notArithmeticException)、オーバーフロー時にMIN_VALUE
またはMAX_VALUE
を返すsaturatingCast
メソッドも同様です。
自分でこの問題にぶつかりましたが、ここに私のソリューションがあります(乗算と加算の両方について):
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;
}
間違っていたり、単純化できる場合は、気軽に修正してください。ほとんどがエッジの場合、乗算方法を使用していくつかのテストを行いましたが、それでも間違っている可能性があります。
安全な算術演算を提供するライブラリがあり、整数のオーバーフロー/アンダーフローをチェックします。たとえば、Guavaの IntMath.checkedAdd(int a、int b) は、オーバーフローしない場合はa
とb
の合計を返し、a + b
が符号付きArithmeticException
算術演算でオーバーフローすると、int
をスローします。
それは回ります。
例えば:
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
私はあなたがこのような何かを使うべきだと思います、それはアップキャスティングと呼ばれます:
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;
}
ここでさらに読むことができます: 整数オーバーフローの検出または防止
非常に信頼できるソースです。
それは何もしません-アンダー/オーバーフローが起こるだけです。
オーバーフローした計算の結果である「-1」は、他の情報から生じた「-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);
}
これでいいと思う。
static boolean addWillOverFlow(int a, int b) {
return (Integer.signum(a) == Integer.signum(b)) &&
(Integer.signum(a) != Integer.signum(a+b));
}
上記に記載されていないケースが1つあります。
int res = 1;
while (res != 0) {
res *= 2;
}
System.out.println(res);
生成されます:
0
このケースはここで議論されました: 整数オーバーフローはゼロを生成します。