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

경우 ab모두 긍정적 인 당신은 사용할 수 있습니다 :

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 = -1b = 10문제 를 해결하려면 아래 내 답변을 참조하십시오.
Jim Pivarski 2015

17

긴 오버플로 / 언더 플로를 확인하는 안전한 산술 연산을 제공하는 Java 라이브러리가 있습니다. 예를 들어 Guava의 LongMath.checkedMultiply (long a, long b) 는 오버플로되지 않는 경우 aand 의 곱을 반환하고 부호있는 산술 에서 오버플로 bArithmeticException발생하면 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_VALUEMAX_VALUE하지만 자바의의 일반입니다 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 배 느립니다. 따라서 코너 케이스에 대한 추가 검사가 성능 측면에서 가치가 있다고 가정합니다.
mhaller 2009

자바 컴파일러에 대해 잘 모르지만, 이와 같은 표현식 a * b / ba다른 많은 컨텍스트에서 최적화 될 가능성이 높습니다 .
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.