Javaは整数のアンダーフローとオーバーフローをどのように処理しますか?
それから、これが起こっていることをどのようにチェック/テストしますか?
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
。最後の 1 つはオーバーフローしません。実際には、使用可能な JVM メモリが限界です。
すでに Java8 を使用している場合は、オーバーフローをスローするnewMath#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
もちろん、ユーティリティ メソッドで非表示にする代わりに、すぐに使用することもできます。
プリミティブな整数型に関する限り、Java はオーバー/アンダーフローをまったく処理しません (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 句の代わりに何をするかは、アプリケーションの要件 (スロー、最小値/最大値へのフラッシュ、または何でもログに記録する) によって異なります。長い操作でオーバーフローを検出したい場合は、プリミティブでは運が悪いので、代わりに 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;
}
(上記の例では、減算オーバーフロー検出に必要な式を使用していることに注意してください)
では、これらのブール式はどのように/なぜ機能するのでしょうか? まず、いくつかの論理的思考により、両方の引数の符号が同じ場合にのみオーバーフローが発生することが明らかになります。1 つの引数が負で 1 つが正の場合、(加算の) 結果は0 に近くなければならず、極端な場合は 1 つの引数が 0 になり、他の引数と同じになります。引数自体はオーバーフロー条件を作成できないため、それらの合計もオーバーフロー条件を作成できません。
では、両方の引数の符号が同じ場合はどうなるでしょうか? 両方が正の場合を見てみましょう: 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) が負の場合にのみ真になります (式は 32 ビットすべてで機能しますが、関心のあるビットのみは最上位 (符号) ビットであり、< 0 によってチェックされます)。
両方の引数が負の場合、それらの合計がどの引数よりもゼロに近くなることはありません。合計は負の無限大に近くなければなりません。生成できる最も極端な値は MIN_VALUE + MIN_VALUE です。これは、(再びバイトの例で) 範囲内の値 (-1 から -128) に対して符号ビットが設定され、オーバーフローする可能性のある値 (-129 から -256) が設定されていることを示しています。 ) の符号ビットはクリアされています。そのため、結果の符号からオーバーフロー状態が再び明らかになります。これは、左半分 (s & d & ~r) が両方の引数 (s, d) が負で、結果が正の場合をチェックするものです。ロジックは、正のケースとほぼ同じです。2 つの負の値を加算した結果として生じる可能性のあるすべてのビット パターンは、アンダーフローが発生した場合に限り、符号ビットがクリアされます。
デフォルトでは、Java の int と long の計算は、オーバーフローとアンダーフローで静かにラップアラウンドします。(他の整数型の整数演算は、JLS 4.2.2に従って、最初にオペランドを int または long に昇格することによって実行されます。)
Java 8 の時点で、指定された操作を実行する int 引数と long 引数の両方に 、、、およびstatic メソッドをjava.lang.Math
提供しaddExact
、オーバーフロー時に ArithmeticException をスローします。(divideExact メソッドはありません。1 つの特殊なケース ( ) を自分で確認する必要があります。)subtractExact
multiplyExact
incrementExact
decrementExact
negateExact
MIN_VALUE / -1
Java 8 以降、java.lang.Math はtoIntExact
long を int にキャストする機能も提供し、long の値が int に収まらない場合は ArithmeticException をスローします。これは、たとえば、チェックされていない long 演算を使用して int の合計を計算toIntExact
し、最後に int にキャストするために使用する場合に役立ちます (ただし、合計がオーバーフローしないように注意してください)。
古いバージョンの Java をまだ使用している場合、Google Guava はチェック付きの加算、減算、乗算、累乗 (オーバーフロー時にスロー) のためのIntMath および LongMath静的メソッドを提供します。これらのクラスには、オーバーフロー時に返される階乗および二項係数を計算するメソッドも用意されてMAX_VALUE
います (これは、チェックするのがあまり便利ではありません)。Guava のプリミティブ ユーティリティ クラスであるSignedBytes
、UnsignedBytes
、Shorts
およびInts
は、より大きな型を絞り込むためのメソッド ( ArithmeticExceptionではなくcheckedCast
、アンダー/オーバーフローで IllegalArgumentException をスローする) と、オーバーフローまたはオーバーフローを返すメソッドを提供します。saturatingCast
MIN_VALUE
MAX_VALUE
Javaは、int型またはlongプリミティブ型のいずれに対しても整数オーバーフローを処理せず、正および負の整数のオーバーフローを無視します。
この回答では、最初に整数のオーバーフローについて説明し、式の評価に中間値がある場合でも、どのように発生するかを示します。次に、整数のオーバーフローを防止および検出するための詳細な手法を提供するリソースへのリンクを示します。
予期しないオーバーフローまたは検出されないオーバーフローが発生する整数演算および式は、一般的なプログラミングエラーです。予期しない、または検出されない整数のオーバーフローも、特に配列、スタック、およびリストオブジェクトに影響を与えるため、悪用可能なセキュリティの問題としてよく知られています。
オーバーフローは、正または負の方向に発生する可能性があり、正または負の値は、問題のプリミティブ型の最大値または最小値を超えます。オーバーフローは、式または操作の評価中に中間値で発生し、最終値が範囲内にあると予想される式または操作の結果に影響を与える可能性があります。
負のオーバーフローは、誤ってアンダーフローと呼ばれることがあります。アンダーフローは、値が表現で許可されているよりもゼロに近い場合に発生します。アンダーフローは整数演算で発生し、予想されます。整数のアンダーフローは、整数の評価が-1から0または0から1の場合に発生します。小数の結果は0に切り捨てられます。これは正常であり、整数演算で予期され、エラーとは見なされません。ただし、コードが例外をスローする可能性があります。1つの例は、整数アンダーフローの結果が式の除数として使用される場合の「ArithmeticException:/byzero」例外です。
次のコードを検討してください。
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の評価は、xに割り当てられる0と評価されます。
xを計算するための式を、評価時に乗算する前に除算する式に再配置すると、次のコードが返されます。
int bigValue = Integer.MAX_VALUE;
int x = bigValue / 5 * 2;
int y = bigValue / x;
その結果、xには858,993,458が割り当てられ、yには2が割り当てられます。これは予想どおりです。
bigValue / 5の中間結果は、intの最大値を超えない429,496,729です。その後の429,496,729*2の評価は、intの最大値を超えず、期待される結果がxに割り当てられます。その場合、yの評価はゼロで除算されません。xとyの評価は期待どおりに機能します。
Java整数値は、2の補数符号付き整数表現として格納され、それに従って動作します。結果の値が最大または最小の整数値よりも大きいまたは小さい場合、代わりに2の補数の整数値が生成されます。ほとんどの通常の整数演算状況である2s補数動作を使用するように明示的に設計されていない状況では、結果の2s補数値により、上記の例に示すようにプログラミングロジックまたは計算エラーが発生します。優れたウィキペディアの記事では、2の補数の2進整数について説明しています。2の補数-ウィキペディア
意図しない整数のオーバーフローを回避するための手法があります。Techinquesは、前提条件テスト、アップキャスト、およびBigIntegerを使用するものとして分類できます。
前提条件テストでは、算術演算または式に入る値を調べて、それらの値でオーバーフローが発生しないことを確認します。プログラミングと設計では、入力値がオーバーフローを引き起こさないことを確認するテストを作成し、オーバーフローを引き起こす入力値が発生した場合の対処方法を決定する必要があります。
アップキャストは、より大きなプリミティブ型を使用して算術演算または式を実行し、結果の値が整数の最大値または最小値を超えているかどうかを判断することで構成されます。アップキャストを使用しても、演算または式の値または中間値がアップキャストタイプの最大値または最小値を超えてオーバーフローを引き起こす可能性があります。これも検出されず、予期しない望ましくない結果が発生します。分析または前提条件により、アップキャストなしの防止が不可能または実用的でない場合に、アップキャストによるオーバーフローを防止できる可能性があります。問題の整数がすでに長いプリミティブ型である場合、Javaのプリミティブ型ではアップキャストはできません。
BigInteger手法は、BigIntegerを使用するライブラリメソッドを使用した算術演算または式にBigIntegerを使用することで構成されます。BigIntegerはオーバーフローしません。必要に応じて、使用可能なすべてのメモリを使用します。その算術演算は通常、整数演算よりもわずかに効率が低くなります。BigIntegerを使用した結果が整数の最大値または最小値を超える可能性はありますが、結果につながる算術演算でオーバーフローは発生しません。プログラミングと設計では、BigIntegerの結果が目的のプリミティブ結果タイプ(intやlongなど)の最大値または最小値を超えた場合の対処方法を決定する必要があります。
カーネギーメロンソフトウェアエンジニアリングインスティテュートのCERTプログラムとオラクルは、安全なJavaプログラミングのための一連の標準を作成しました。標準には、整数のオーバーフローを防止および検出するための手法が含まれています。この標準は、ここで自由にアクセスできるオンラインリソースとして公開されています:CERT Oracle Secure Coding Standard for Java
整数のオーバーフローを防止または検出するためのコーディング手法の実際的な例を説明および含む標準のセクションは、NUM00-Jです。整数のオーバーフローを検出または防止する
CERT Oracle Secure Coding StandardforJavaのブックフォームとPDFフォームも利用できます。
自分でこの問題に少し遭遇したので、これが私の解決策です(乗算と加算の両方):
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;
}
間違っている場合、または簡略化できる場合は、遠慮なく修正してください。私は乗算法でいくつかのテストを行いましたが、ほとんどはエッジケースですが、それでも間違っている可能性があります。
回り込みます。
例えば:
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
整数のオーバーフロー/アンダーフローをチェックする安全な算術演算を提供するライブラリがあります。たとえば、Guava のIntMath.checkedAdd(int a, int b)は、オーバーフローしない場合はa
andの合計を返し、符号付き演算でオーバーフローした場合はスローします。b
ArithmeticException
a + b
int
このようなものを使用する必要があると思います。これはアップキャスティングと呼ばれます。
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
このケースについては、ここで説明しました: 整数オーバーフローはゼロを生成します。