Як я можу перевірити, чи перемноження двох чисел у 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
Дуже погано, що Java не надає непрямий доступ до переповненого прапора CPU , як це робиться в C # .
Дрю Ноакс

Відповіді:


92

Java 8 має Math.multiplyExactі Math.addExactт.д. для ints і long. Вони кидають неперевірений 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 завжди є негативними, що дещо спростить такий підхід.
Стів Маклеод

3
Я думаю, що це може спричинити невдачу: a = -1 і b = 10. Максимальний / виразний результат призводить до Integer.MIN_VALUE і виявляє переповнення, коли його не було
Kyle

Це справді приємно. Для тих , кому цікаво, чому це працює в тому , що для цілого числа n, n > xтаке ж , як n > floor(x). Для позитивних цілих чисел діють неявні підлоги. (Для від’ємних чисел він округляється натомість)
Томас Ейл

Щоб вирішити a = -1та b = 10вирішити проблему, дивіться мою відповідь нижче.
Джим Піварський

17

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


4
Це найкраща відповідь - використовуйте бібліотеку, яку реалізовували люди, які справді розуміють машинну арифметику на Java і яку перевірили багато людей. Не намагайтеся написати свій власний або використовувати будь-який напівфабрикат неперевірений код, розміщений в інших відповідях!
Багатий

@Enerccio - я не розумію твій коментар. Ви хочете сказати, що Guava працюватиме не в усіх системах? Я можу запевнити, що вона працюватиме скрізь, де працює Java. Ви хочете сказати, що повторне використання коду взагалі погана ідея? Я не згоден, якщо так.
Багатий

2
@Rich Я кажу, включаючи величезну бібліотеку, щоб ви могли використовувати одну функцію - це погана ідея.
Enerccio

Чому? Якщо ви пишете велику заявку, наприклад, для бізнесу, то додатковий JAR на classpath не принесе ніякої шкоди, і Guava містить у собі дуже багато корисного коду. Набагато краще повторно використовувати їх ретельно перевірений код, ніж намагатися написати свою власну версію того самого (який я вважаю, що ви рекомендуєте?). Якщо ви пишете в середовищі, коли зайвий JAR буде дуже дорогим (де? Вбудована Java?), То, можливо, вам слід витягти саме цей клас з Guava. Чи краще копіювання неперевіреної відповіді з StackOverflow, ніж копіювання ретельно перевіреного коду Guava?
Багатий

Чи не викинути виняток трохи зайвої навантаження на щось умовне, якщо тоді можна впоратися?
віктим

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
Я вважаю це рішення досить повільним
невдалий

Це, мабуть, найкращий спосіб зробити це. Я припускав, що це чисельний додаток, і тому я не рекомендував його накладно, але це, мабуть, найкращий спосіб вирішити цю проблему.
Стефан Кендалл

2
Я не впевнений, що ви можете використовувати оператор '>' з BigInteger. слід застосовувати метод CompareTo.
П’єр

змінено на порівнянняТож, а швидкість може мати значення чи не може, залежить від обставин, коли буде використаний код.
Ulf Lindback

5

Використовуйте логарифми, щоб перевірити розмір результату.


Ви маєте в виду: ceil(log(a)) + ceil(log(b)) > log(Long.MAX)?
Томас Юнг

1
Я перевірив це. Для малих значень це на 20% швидше, ніж BigInteger, а для значень поблизу MAX - майже те саме (на 5% швидше). Код Йоссаріана найшвидший (на 95% і 75% швидше, ніж BigInteger).
Томас Юнг

Я скоріше підозрюю, що в деяких випадках це може вийти з ладу.
Том Хотін - тайклін

Пам'ятайте, що цілий журнал фактично просто підраховує кількість перших нулів, і ви можете оптимізувати деякі поширені випадки (наприклад, якщо ((a | b) & 0xffffffff00000000LL == 0) ви знаєте, що ви в безпеці). З іншого боку, якщо ви не зможете оптимізувати до 30/40-годинних тактових циклів для найчастіших випадків, метод Джона Кугельмана, ймовірно, буде краще (ціле ділення становить приблизно 2 біта / тактовий цикл, наскільки я пам'ятаю).
Ніл Коффі

PS Sorr, я думаю, мені потрібен додатковий набір у масці AND (0xffffffff80000000L) - це трохи пізно, але ви розумієте ...
Ніл Коффі

4

Чи має Java щось подібне до int.MaxValue? Якщо так, то спробуйте

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

редагувати: переглянуто Long.MAX_VALUE, про який йде мова


Я не сказав, але Math.Abs(a)не працює, якщо aєLong.MIN_VALUE .
Джон Кугельман

@John - a і b є> 0. Я думаю, що підхід Йоссаріана (b! = 0 && a> Long.MAX_VALUE / b) є найкращим.
Томас Юнг

@Thomas, a і b є> = 0, тобто невід'ємні.
Стів Маклеод

2
Правильно, але в цьому випадку немає необхідності в АБС. Якщо дозволено від'ємне число, то це не вдається принаймні для одного крайнього випадку. Це все, що я говорю, просто бути нерозумним.
Джон Кугельман

на Java ви повинні використовувати Math.abs, а не Math.Abs ​​(хлопець C #?)
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

Вкрадено з джурбі

    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
}

Зауважте, що це спричинить деякі переливи без будь-якого поділу.


Ви можете використовувати Long.signum замість Math.signum
aditsu киньте, тому що SE - EVIL

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

тощо.

ОНОВЛЕНО: вимкнули недійсні посилання на сторонній веб-сайт до посилань на сховища Mercurial Open JDK.


2

Я не впевнений, чому ніхто не дивиться на таке рішення, як:

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

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


2
Я не думаю, що це має значення, чи aбільший, чи менший?
Томас Ейл

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)

Застосовуючи справжнє 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.


Я просто думав, що це зайво ускладнить це, оскільки це рішення працює лише в тому випадку MIN_VALUE = -MAX_VALUE - 1, якщо не будь-який інший випадок (включаючи ваш приклад тестового випадку). Мені довелося б багато чого змінити.
Джим Піварський

1
З причин, що виходять за потреби оригінального афіші; для таких людей, як я, які знайшли цю сторінку, оскільки вони потребували рішення для більш загального випадку (обробка негативних цифр, а не суто Java 8). Насправді, оскільки це рішення не передбачає жодних функцій, що виходять за межі чистої арифметики та логіки, воно може використовуватися і для C, або для інших мов.
Джим Піварський

1

Може бути:

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

Не впевнений у цьому "рішенні".

Редагувати: Додано b! = 0.

Перш ніж звернути увагу : a * b / b не буде оптимізовано. Це буде помилка компілятора. Я досі не бачу випадку, коли помилку переповнення можна замаскувати.


Також не вдається, коли переповнення викликає ідеальну петлю.
Стефан Кендалл

Чи є у вас приклад того, що ви мали на увазі?
Томас Юнг

Щойно написав невеликий тест: Використання BigInteger у 6 разів повільніше, ніж використання цього підходу. Тож я припускаю, що додаткові перевірки на кутові справи варті того, щоб зробити їх ефективними.
mhaller

Не знаю багато про компілятори java, але такий вираз a * b / b, ймовірно, буде оптимізований саме aдля багатьох інших контекстів.
SingleNegationElimination

TokenMacGuy - його неможливо оптимізувати, якщо є небезпека переповнення.
Том Хотін - тайклін

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.
Том Хотін - тайклін

1
Ви це навіть тестували? Для будь-яких великих, але не переповнених значень & b це не вдасться через помилки округлення у подвійній версії множення (спробуйте, наприклад, 123456789123L та 74709314L). Якщо ви не розумієте машинну арифметику, вгадувати відповідь на такий вид точного питання гірше, ніж не відповідати, оскільки це введе в оману людей.
Багатий

-1

c / c ++ (довгий * довгий):

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 покладе результат на long, long * long put to int64)

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.