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에 될 일 경우에, 당신은 새로운 사용 할 수 있습니다 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
유틸리티 메소드 에 숨기지 않고 바로 사용할 수도 있습니다 .
답변
음, 원시 정수 유형에 이르기까지 Java는 Over / Underflow를 전혀 처리하지 않습니다 (float과 double의 동작이 다르면 IEEE-754 명령과 같이 +/- 무한대로 플러시됩니다).
두 개의 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;
}
(위의 예는 빼기 오버플로 감지에 필요한 표현을 사용합니다 )
그렇다면 이러한 부울 표현식은 어떻게 / 왜 작동합니까? 첫째, 일부 논리적 사고는 두 인수의 부호가 동일한 경우 에만 오버플로가 발생할 수 있음을 나타냅니다 . 하나의 인수가 음수이고 하나의 양수인 경우 추가의 결과 는 0에 가까워 지거나 극단적 인 경우 하나의 인수는 다른 인수와 같은 0이어야합니다. 스스로 인수가 있기 때문에 할 수없는 오버 플로우 조건을 만들고, 그 합은 하나 오버 플로우를 만들 수 없습니다.
두 인수 모두 같은 부호를 가지면 어떻게됩니까? MAX_VALUE 유형보다 더 큰 합계를 생성하는 두 개의 인수를 추가하면 항상 음수 값이 생성되므로 arg1 + arg2> MAX_VALUE 인 경우 오버플로가 발생합니다 . 이제 발생할 수있는 최대 값은 MAX_VALUE + MAX_VALUE입니다 (극단의 경우 두 인수 모두 MAX_VALUE). 127 + 127 = 254를 의미하는 바이트 (예)의 경우 두 개의 양수 값을 추가하여 발생할 수있는 모든 값의 비트 표현을 살펴보면 오버플로 된 (128-254) 비트가 모두 7로 설정되어 있음을 알 수 있습니다. 오버플로되지 않는 (0-127) 비트 7 (맨 위, 부호)이 지워졌습니다. 그것이 표현식의 첫 번째 (오른쪽) 부분이 정확히 확인하는 것입니다.
if (((s & d & ~r) | (~s & ~d & r)) < 0)
(~ s & ~ d & r)은 피연산자 (s, d) 모두 양수이고 결과 (r)가 음수 인 경우에만 적용됩니다 (표현식은 모든 32 비트에서 작동하지만 우리가 관심있는 유일한 비트는 가장 최상위 (부호) 비트이며 <0에 의해 검사됩니다).
이제 두 인수가 모두 음수이면 합계가 인수보다 0에 가까워 질 수 없으며, 합계 는 마이너스 무한대에 가까워 야합니다 . 우리가 생성 할 수있는 가장 극단적 인 값은 MIN_VALUE + MIN_VALUE이며, 바이트 예제의 경우 범위 값 (-1 ~ -128)에 대해 부호 비트가 설정되어 있지만 오버플로 값 (-129 ~ -256)이 표시됩니다 )의 부호 비트가 지워졌습니다. 따라서 결과의 부호는 다시 오버플로 조건을 나타냅니다. 왼쪽 절반 (s & d & ~ r)은 두 인수 (s, d)가 모두 음수이고 결과가 긍정적 인 경우를 확인합니다. 논리는 긍정적 인 경우와 거의 같습니다. 두 개의 음수 값을 추가하여 발생할 수있는 모든 비트 패턴 은 언더 플로가 발생한 경우에만 부호 비트가 지워집니다 .
답변
기본적으로 Java의 int 및 long math는 오버플로 및 언더 플로를 자동으로 감 쌉니다. (다른 정수 유형에 대한 정수 연산은 먼저 JLS 4.2.2에 따라 피연산자를 int 또는 long으로 승격하여 수행됩니다 .)
자바 8, 현재로 java.lang.Math
제공 addExact
, subtractExact
, multiplyExact
, incrementExact
, decrementExact
및 negateExact
INT와 오버 플로우에 arithmeticexception이 던지는 명명 된 작업을 수행 긴 인수를 모두 정적 방법. (divideExact 메소드는 없습니다. 하나의 특별한 경우를 MIN_VALUE / -1
직접 확인해야합니다 .)
Java 8부터 java.lang.Math는 toIntExact
long을 int로 캐스팅하여 long 값이 int에 맞지 않으면 ArithmeticException을 발생시킵니다. 이것은 예를 들어 확인되지 않은 긴 수학을 사용하여 정수의 합계를 계산 한 다음 toIntExact
끝에 int로 캐스트하는 데 사용 하지만 유용합니다 (그러나 합계가 오버플로되지 않도록주의하십시오).
이전 버전의 Java를 계속 사용하는 경우 Google Guava는 확인 된 덧셈, 뺄셈, 곱셈 및 지수화 (오버플로 발생)를 위해 IntMath 및 LongMath 정적 메소드를 제공합니다 . 이 클래스는 또한 MAX_VALUE
오버플로시 리턴 되는 계승 및 이항 계수를 계산하는 메소드를 제공합니다 (확인하기가 덜 편리함). 구아바의 원시 유틸리티 클래스는, SignedBytes
, UnsignedBytes
, Shorts
및 Ints
제공 checkedCast
큰 유형 (아래 / 오버 플로우에, IllegalArgumentException를 던지는 축소하는 방법 하지 않고 ArithmeticException를)뿐만 아니라 saturatingCast
방법 돌려 MIN_VALUE
또는 MAX_VALUE
오버 플로우에 있습니다.
답변
Java는 int 또는 long 기본 유형의 정수 오버플로로 아무것도하지 않으며 양수 및 음수로 오버플로를 무시합니다.
이 답변은 먼저 정수 오버플로에 대해 설명하고 식 평가의 중간 값으로도 어떻게 발생할 수 있는지에 대한 예를 제공 한 다음 정수 오버플로를 방지하고 감지하기위한 자세한 기술을 제공하는 리소스에 대한 링크를 제공합니다.
예기치 않은 또는 감지되지 않은 오버플로에서 발생하는 정수 산술 및 식은 일반적인 프로그래밍 오류입니다. 예기치 않거나 감지되지 않은 정수 오버플로는 특히 배열, 스택 및 목록 객체에 영향을 미치므로 잘 알려진 악용 가능한 보안 문제입니다.
양수 또는 음수 값이 해당 기본 유형의 최대 값 또는 최소값을 초과하는 양수 또는 음수 방향으로 오버플로가 발생할 수 있습니다. 오버플로는 식 또는 연산 평가 중 중간 값에서 발생할 수 있으며 최종 값이 범위 내에있을 것으로 예상되는 식 또는 연산의 결과에 영향을줍니다.
때때로 부정적인 오버플로는 언더 플로라고 잘못 부릅니다. 언더 플로는 표현이 허용하는 것보다 값이 0에 가까울 때 발생합니다. 언더 플로는 정수 산술로 발생하며 예상됩니다. 정수 언더 플로우는 정수 평가가 -1과 0 또는 0과 1 사이 일 때 발생합니다. 소수 결과는 0으로 잘립니다. 이는 정수 산술에서는 정상이며 오류로 간주되지 않습니다. 그러나 코드가 예외를 발생시킬 수 있습니다. 정수 언더 플로의 결과가 식의 제수로 사용되는 경우 “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”(예 : 0으로 나눔) 예외가 발생합니다.
x에 대한 예상 결과는 858,993,458이며 최대 int 값 2,147,483,647보다 작습니다. 그러나 Integer.MAX_Value * 2 평가의 중간 결과는 최대 int 값을 초과하고 2의 보수 정수 표현에 따라 -2 인 4,294,967,294입니다. -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에 대한 평가는 0으로 나누지 않습니다. x와 y에 대한 평가는 예상대로 작동합니다.
Java 정수 값은 2의 보수 부호있는 정수 표현에 따라 저장되고 동작합니다. 결과 값이 최대 또는 최소 정수 값보다 크거나 작을 경우 2의 보수 정수 값이 대신 발생합니다. 가장 일반적인 정수 산술 상황 인 2s 보수 동작을 사용하도록 명시 적으로 설계되지 않은 상황에서는 결과 2s 보수 값으로 인해 위의 예와 같이 프로그래밍 논리 또는 계산 오류가 발생합니다. 훌륭한 Wikipedia 기사에서 2의 칭찬 이진 정수에 대해 설명합니다 : 2의 보수-Wikipedia
의도하지 않은 정수 오버플로를 피하는 기술이 있습니다. Techinque는 사전 조건 테스트, 업 캐스팅 및 BigInteger를 사용하여 분류 될 수 있습니다.
사전 조건 테스트는 산술 연산 또는 표현식으로 들어가는 값을 검사하여 해당 값으로 오버플로가 발생하지 않는지 확인합니다. 프로그래밍과 디자인은 입력 값이 오버플로를 유발하지 않도록 테스트를 생성 한 다음 입력 값이 발생하면 오버플로를 유발할 조치를 결정해야합니다.
업 캐스팅은 더 큰 기본 유형을 사용하여 산술 연산 또는 표현식을 수행 한 다음 결과 값이 정수의 최대 값 또는 최소값을 초과하는지 여부를 판별합니다. 업 캐스팅을 수행하더라도 조작 또는 표현식의 값 또는 일부 중간 값이 업 캐스트 유형의 최대 값 또는 최소값을 초과하여 오버 플로우가 발생하여 오버 플로우가 발생할 수 있으며, 이는 감지되지 않으며 예기치 않은 원하지 않는 결과를 초래할 수 있습니다. 업 캐스팅없이 예방할 수 없거나 실용적이지 않은 경우 분석 또는 사전 조건을 통해 업 캐스팅으로 오버플로를 방지 할 수 있습니다. 해당 정수가 이미 긴 기본 유형 인 경우 Java의 기본 유형으로 업 캐스팅 할 수 없습니다.
BigInteger 기술은 BigInteger를 사용하는 라이브러리 메소드를 사용하여 산술 연산 또는 표현식에 BigInteger를 사용하는 것으로 구성됩니다. BigInteger가 오버 플로우되지 않습니다. 필요한 경우 사용 가능한 모든 메모리를 사용합니다. 산술 방법은 일반적으로 정수 연산보다 약간 덜 효율적입니다. BigInteger를 사용한 결과가 정수의 최대 값 또는 최소값을 초과 할 수 있지만 결과로 이어지는 산술에서는 오버 플로우가 발생하지 않습니다. 프로그래밍 및 디자인은 BigInteger 결과가 원하는 기본 결과 유형 (예 : int 또는 long)의 최대 값 또는 최소값을 초과하는 경우 수행 할 조치를 결정해야합니다.
Carnegie Mellon Software Engineering Institute의 CERT 프로그램과 Oracle은 안전한 Java 프로그래밍을위한 일련의 표준을 만들었습니다. 정수 오버플로를 방지하고 감지하는 기술이 표준에 포함되어 있습니다. 이 표준은 자유롭게 액세스 할 수있는 온라인 리소스로 게시됩니다. Java 용 CERT Oracle Secure Coding Standard
정수 오버플로 방지 또는 감지를위한 코딩 기술의 실제 예를 설명하고 포함하는 표준 섹션은 다음과 같습니다. NUM00-J. 정수 오버플로 감지 또는 방지
CERT Oracle Secure Coding Standard for Java의 서적 및 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;
}
잘못되었거나 단순화 될 수 있으면 자유롭게 수정하십시오. 나는 대부분의 경우에 곱셈 방법으로 테스트를했지만 여전히 잘못 될 수 있습니다.
답변
정수 오버 플로우 / 언더 플로우를 검사하는 안전한 산술 연산을 제공하는 라이브러리가 있습니다. 예를 들어, Guava의 IntMath.checkedAdd (int a, int b) 는 오버플로되지 않은 경우 a
및 의 합을 반환하고 부호있는 산술 에서 오버플로 b
가 ArithmeticException
발생하면 throw 됩니다 .a + b
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