Как я могу проверить, если умножение двух чисел в Java вызовет переполнение?


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

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 87

14 ответов:

Java 8 имеет Math.multiplyExact,Math.addExact etc. для ИНЦ и долго. Они бросают непроверенный ArithmeticException при переполнении.

если 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

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

вы можете использовать java.математика.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()
}

использовать логарифмы для проверки размера результата.

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

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

редактировать: видно долго.Массив в вопрос

украдено у jruby

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

UPDATE: этот код короткий и хорошо работает; однако он не работает для 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
}

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

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

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

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

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

может быть:

if(b!= 0 && a * b / b != a) //overflow

Не уверен в этом "решении".

Edit: добавлено b != 0.

перед вами downvote: a * b / b не будет оптимизирован. Это будет ошибка компилятора. Я все еще не вижу случая, когда ошибка переполнения может быть замаскирована.

может быть, это поможет вам:

/**
 * @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;
    }
}

как было указано, Java 8 имеет математику.xxxExact методы, которые создают исключения при переполнении.

Если вы не используете Java 8 для своего проекта, вы все равно можете "заимствовать" их реализации, которые довольно компактны.

вот некоторые ссылки на эти реализации на стороннем веб-сайте, нет гарантии, что они останутся действительными, но в любом случае вы должны быть в состоянии войти в источник JDK и посмотреть, как они делают свою магию внутри java.lang.Math класс.

Math.multiplyExact(long, long) http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/java/lang/Math.java?av=f#882

Math.addExact http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/java/lang/Math.java?av=f#805

etc, etc.

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

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

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, извините, что не нашел int64 в java):

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 и поместить результат на долгое, долгое*долгий путь до int64)

2.результат cmp > > бит и результат >> (бит-1)