Как я могу проверить, приведет ли умножение двух чисел в Java к переполнению? - PullRequest
92 голосов
/ 01 ноября 2009

Я хочу обработать специальный случай, когда умножение двух чисел вызывает переполнение. Код выглядит примерно так:

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 всегда неотрицательны в моем сценарии.

Ответы [ 14 ]

80 голосов
/ 24 сентября 2014

Java 8 имеет Math.multiplyExact, Math.addExact и т. Д. Для целых и длинных. Они выбрасывают непроверенный ArithmeticException при переполнении.

59 голосов
/ 01 ноября 2009

Если 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
17 голосов
/ 05 декабря 2012

Существуют библиотеки Java, которые обеспечивают безопасные арифметические операции, которые проверяют длительное переполнение / переполнение. Например, Guava *1001* LongMath.checkedMultiply (long a, long b) возвращает произведение a и b при условии, что оно не переполняется, и выдает ArithmeticException, если a * b переполняется в знаке long арифметика.

6 голосов
/ 01 ноября 2009

Используйте логарифмы, чтобы проверить размер результата.

6 голосов
/ 01 ноября 2009

Вместо этого вы можете использовать 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()
}
4 голосов
/ 01 ноября 2009

Есть ли в Java что-то вроде int.MaxValue? Если да, то попробуйте

if (b != 0 && Math.abs(a) > Math.abs(Long.MAX_VALUE / b))
{
 // it will overflow
}

редактировать: видно Long.MAX_VALUE в вопросе

3 голосов
/ 25 августа 2017

Вот самый простой способ, которым я могу придумать

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 голосов
/ 01 июня 2011

украдено у Джруби

    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
}

Обратите внимание, что это будет перехватывать некоторые переполнения без деления.

2 голосов
/ 03 ноября 2015

Я хотел бы опираться на ответ Джона Кугельмана, не заменяя его, редактируя его напрямую. Это работает для его тестового примера (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)

При применении к истинным MIN_VALUE и MAX_VALUE ответ Джона Кугельмана дает случай переполнения, когда a == -1 и b == что-либо еще (точка, впервые поставленная Кайлом) Вот способ исправить это:

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, но оно является общим для Java Long и Integer и любого значения a и b.

2 голосов
/ 01 ноября 2009

Я не уверен, почему никто не смотрит на решение вроде:

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

Выберите a, чтобы быть большим из двух чисел.

...