Java에서 두 숫자를 곱하면 오버플로가 발생하는지 어떻게 확인할 수 있습니까?


101

두 숫자를 곱하면 오버플로가 발생하는 특수한 경우를 처리하고 싶습니다. 코드는 다음과 같습니다.

int a = 20;
long b = 30;

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

그것은 단순화 된 버전입니다. 실제 프로그램에서 a그리고 b런타임에 다른 곳에서 소싱됩니다. 내가 달성하고 싶은 것은 다음과 같습니다.

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

이것을 가장 잘 코딩하는 방법은 무엇입니까?

업데이트 : a그리고 b내 시나리오에서 음이 아닌 항상이다.


6
C # 에서처럼 Java가 CPU의 오버플로 플래그에 대한 간접 액세스를 제공하지 않는 것이 너무 나쁩니다 .
— Drew Noakes 2012-08-20

답변:


92

자바 (8)이 Math.multiplyExact, Math.addExactint 치의과 긴 등. 이것들은 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는 항상 음수가 아니므로이 접근 방식을 다소 단순화 할 수 있습니다.
— Steve McLeod

3
나는 이것이 경우에 실패 할 수 있다고 생각합니다 : a = -1 및 b = 10. 최대 / a 표현식은 Integer.MIN_VALUE를 생성하고 존재하지 않을 때 오버플로를 감지합니다
— Kyle

이것은 정말 좋습니다. 궁금 사람들을 위해,이 작품 이유는 정수이다 n, n > x과 동일합니다 n > floor(x). 양의 정수의 경우 나눗셈은 암시 적 바닥을합니다. (음수의 경우 대신 반올림)
— Thomas Ahle

a = -1및 b = 10문제 를 해결하려면 아래 내 답변을 참조하십시오.
— Jim Pivarski 2015

17

긴 오버플로 / 언더 플로를 확인하는 안전한 산술 연산을 제공하는 Java 라이브러리가 있습니다. 예를 들어 Guava의 LongMath.checkedMultiply (long a, long b) 는 오버플로되지 않는 경우 aand 의 곱을 반환하고 부호있는 산술 에서 오버플로 b가 ArithmeticException발생하면 throw 합니다 .a * blong


4
이것이 가장 좋은 대답입니다. 자바로 기계 산술을 정말로 이해하는 사람들이 구현하고 많은 사람들이 테스트 한 라이브러리를 사용하십시오. 직접 작성하거나 다른 답변에 게시 된 반쯤 구운 테스트되지 않은 코드를 사용하지 마십시오!
— Rich

@Enerccio-귀하의 의견을 이해할 수 없습니다. Guava가 모든 시스템에서 작동하지 않는다는 말입니까? Java가 수행하는 모든 곳에서 작동한다는 것을 확신 할 수 있습니다. 코드를 재사용하는 것이 일반적으로 나쁜 생각이라는 말입니까? 그렇다면 동의하지 않습니다.
— Rich

2
@Rich 하나의 기능을 사용할 수 있도록 거대한 라이브러리를 포함하는 것은 나쁜 생각입니다.
— Enerccio

왜? 예를 들어 비즈니스를 위해 대규모 응용 프로그램을 작성하는 경우 클래스 경로의 추가 JAR은 해를 끼치 지 않으며 Guava에는 매우 유용한 코드가 많이 있습니다. 자신의 버전을 작성하는 것보다 신중하게 테스트 한 코드를 재사용하는 것이 훨씬 낫습니다 (이것이 권장하는 것이라고 생각합니까?). 추가 JAR이 매우 비싼 환경 (어디에 임베디드 Java?)에서 작성하는 경우 Guava에서이 클래스 만 추출해야합니다. Guava의 신중하게 테스트 된 코드를 복사하는 것보다 StackOverflow에서 테스트되지 않은 답변을 복사하는 것이 더 낫습니까?
— Rich

조건부 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 메서드를 사용해야합니다.
— Pierre

compareTo로 변경되고 속도는 중요하거나 중요하지 않을 수 있으며 코드가 사용되는 상황에 따라 다릅니다.
— Ulf Lindback

5

로그를 사용하여 결과의 ​​크기를 확인하십시오.


의미 ceil(log(a)) + ceil(log(b)) > log(Long.MAX)합니까 : ?
— Thomas Jung

1
확인했습니다. 작은 값의 경우 BigInteger보다 20 % 빠르며 MAX에 가까운 값의 경우 거의 동일합니다 (5 % 빠름). Yossarian의 코드가 가장 빠릅니다 (BigInteger보다 95 % 및 75 % 빠름).
— Thomas Jung

오히려 어떤 경우에는 실패 할 수 있다고 생각합니다.
— Tom Hawtin-tackline

정수 로그는 효과적으로 선행 0의 수를 세는 것이며, 몇 가지 일반적인 경우를 최적화 할 수 있습니다 (예 : ((a | b) & 0xffffffff00000000L) == 0) 안전하다는 것을 알고 있음). 반면에, 가장 일반적인 경우에 대해 최적화를 30 / 40-ish 클럭 사이클로 낮출 수 없다면 John Kugelman의 방법이 아마도 더 잘 수행 될 것입니다 (제가 기억하는 것처럼 정수 분할은 c. 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 확인


나는 downvote하지하지만 않은 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, 즉 음이 아닙니다.
— Steve McLeod

2
맞아요,하지만이 경우에는 복근이 필요 없습니다. 음수가 허용되면 하나 이상의 에지 케이스에 대해 실패합니다. 그게 내가 말하는 전부입니다.
— John Kugelman

Java에서는 Math.Abs ​​(C # guy?)가 아닌 Math.abs를 사용해야합니다.
— dfa

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

jruby에서 도난 당함

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

업데이트 :이 코드는 짧고 잘 작동합니다. 그러나 a = -1, b = Long.MIN_VALUE에 대해 실패합니다.

한 가지 가능한 개선 사항 :

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 악이기 때문에

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

기타 등등.

업데이트 : 타사 웹 사이트에 대한 잘못된 링크를 Open JDK의 Mercurial 저장소에 대한 링크로 전환했습니다.


2

아무도 다음과 같은 해결책을 보지 않는 이유를 모르겠습니다.

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

두 숫자 중 더 큰 숫자를 선택하십시오.


2
a더 크든 작든 상관 없다고 생각합니다 .
— Thomas Ahle

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_VALUE및에 적용될 때 MAX_VALUEJohn Kugelman의 답변은 when a == -1and any other b ==(Kyle이 처음 제기 한 포인트) 오버플로 케이스를 생성합니다 . 문제를 해결하는 방법은 다음과 같습니다.

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_VALUE및 MAX_VALUE하지만 자바의의 일반입니다 Long및 Integer및의 값 a과 b.


이 솔루션은 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 배 느립니다. 따라서 코너 케이스에 대한 추가 검사가 성능 측면에서 가치가 있다고 가정합니다.
— mhaller 2009

자바 컴파일러에 대해 잘 모르지만, 이와 같은 표현식 a * b / b은 a다른 많은 컨텍스트에서 최적화 될 가능성이 높습니다 .
— SingleNegationElimination 2009

TokenMacGuy-오버플로의 위험이있는 경우 그렇게 최적화 할 수 없습니다.
— Tom Hawtin-tackline

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

피연산자 중 하나가이므로 도움이되지 않습니다 long.
— Tom Hawtin-tackline

1
이걸 테스트 했어? 크지 만 넘치지 않는 a 및 b 값의 경우 곱셈의 이중 버전에서 반올림 오류로 인해 실패합니다 (예 : 123456789123L 및 74709314L 시도). 기계 산술을 이해하지 못한다면, 이런 종류의 정확한 질문에 대한 답을 추측하는 것이 사람들을 오도 할 수 있기 때문에 답하지 않는 것보다 더 나쁩니다.
— Rich

-1

c / c ++ (long * long) :

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.