Как предотвратить переполнение целых чисел в Java-коде?
Возможный дубликат:
Как проверить, может ли перемножение двух чисел в Java вызвать переполнение?
Предположим, что у меня есть метод класса Java, который использует операции *
и +
.
int foo(int a, int b) {
... // some calculations with + and *
}
Как убедиться, что переполнение не происходит в foo
?
Я думаю, я могу либо использовать BigDecimal
, либо заменить все + и * на "обертки", например:
int sum(int a, int b) {
int c = a + b;
if (a > 0 && b > 0 && c < 0)
throw new MyOverfowException(a, b)
return c;
}
int prod(int a, int b) {
int c = a * b;
if (a > 0 && b > 0 && c < 0)
throw new MyOverfowException(a, b)
return c;
}
Есть ли лучшие способы убедиться, что в методе Java не происходит переполнение int
?
Ответы
Ответ 1
Это сложная проблема с технической точки зрения.
Сайт Безопасное кодирование рекомендует:
- использование предварительных условий; то есть контролировать диапазон входов, чтобы невозможно было переполнение,
- выполнение каждой отдельной арифметической операции с использованием следующего более простого примитивного целочисленного типа и явная проверка для переполнения или
- с помощью BigInteger.
Эта статья Dr Dobbs предлагает создать библиотеку примитивных арифметических методов, которые выполняют каждую примитивную операцию с явной проверкой переполнения. (Вы могли бы рассмотреть это как реализацию маркерной точки № 2 выше.) Но авторы идут дальше, предлагая вам использовать переработку байт-кода для замены арифметических байт-кодов вызовами эквивалентных методов, которые включают проверки переполнения.
К сожалению, нет возможности включить проверку переполнения в Java. (Но то же самое относится к множеству других языков, например C, С++...)
Ответ 2
Один из способов проверки переполнения состоит в том, чтобы иметь операнды, продвигаемые к большему типу (с удвоенной исходной длиной бит операнда), затем выполнить операцию, а затем посмотреть, слишком ли велико результирующее значение для исходного типа, например
int sum(int a, int b) {
long r = (long)a + b;
if (r >>> 32 != 0) { // no sign extension
throw new MyOverflowException(a, b);
}
return (int)r;
}
Если ваш оригинальный тип - long
, вам следует использовать BigInteger
как этот более крупный тип.
Ответ 3
Сумма: проверьте, больше ли b больше разницы максимального значения, которое вы можете сохранить в int минус значение a. Если a и/или b могут быть отрицательными, вы должны (i) быть осторожными, чтобы не получить переполнение уже для проверки разницы и (ii) выполнить аналогичную проверку для минимума.
Продукт: это сложнее. Я бы разложил целые числа на два целых числа полуразряд (т.е. Если int 32 бит, разделил его на два 16-битных числа, используя бит-маскирование и смещение). Затем выполните умножение, а затем посмотрите, подходит ли результат в 32 бит.
Все, при условии, что вы не хотите просто взять long
для временного результата.
Ответ 4
Предположим, что a и b положительны или отрицательны, и если знак a + b не равен знаку a и b, то происходит переполнение. Это правило можно использовать для определения того, происходит ли переполнение и генерирует исключение. Когда вы поймаете этот эксцесс, вы можете справиться с ним в соответствии с методом, указанным в предыдущих ответах.
Другим методом является операция, использующая самый большой тип диапазона, который не будет переполняться. Вы можете использовать long для операции между целыми числами.