Как я могу проверить, если умножение двух чисел в 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 ответов:
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
переполняется в signedlong
арифметика.
вы можете использовать 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
, но это вообще для JavaLong
и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#805etc, 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)