Ответ 1
Java 8 имеет Math.multiplyExact
, Math.addExact
и т.д. для int и long. Они бросают непроверенный ArithmeticException
при переполнении.
Я хочу обработать специальный случай, когда умножение двух чисел вместе вызывает переполнение. Код выглядит примерно так:
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
всегда неотрицательны в моем сценарии.
Java 8 имеет Math.multiplyExact
, Math.addExact
и т.д. для int и long. Они бросают непроверенный 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, которые обеспечивают безопасные арифметические операции, которые проверяют длинные переполнения/недополнения. Например, Guava LongMath.checkedMultiply(long a, long b) возвращает произведение a
и b
при условии, что оно не переполняется, и выбрасывает ArithmeticException
, если a * b
переполняется в знаке long
арифметика.
Вместо этого вы можете использовать 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()
}
Используйте логарифмы, чтобы проверить размер результата.
Есть ли у Java что-то вроде int.MaxValue? Если да, попробуйте
if (b != 0 && Math.abs(a) > Math.abs(Long.MAX_VALUE / b))
{
// it will overflow
}
Изменить: видели Long.MAX_VALUE в вопросе
Украден из 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
}
Обратите внимание, что это приведет к переполнению без какого-либо деления.
Вот простейший способ, которым я могу думать о
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"
}
Я не уверен, почему никто не ищет решения вроде:
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)
При применении к истинным 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
.
Как уже указывалось, в 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.
Может быть:
if(b!= 0 && a * b / b != a) //overflow
Не уверен в этом "решении".
Изменить: Добавлено 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;
}
}
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.save результат в большом типе (int * int помещает результат в long, long * long put to int64)
Результат 2.cmp → бит и результат → (бит - 1)