Как Java обрабатывает целочисленные неполные потоки и переполнения и как бы вы это проверили?
Как Java обрабатывает целочисленные неполные потоки и переполнения?
Исходя из этого, как бы вы проверили / протестировали, что это происходит?
Переведено автоматически
Ответ 1
Если оно переполняется, оно возвращается к минимальному значению и продолжается оттуда. Если он переполняется, он возвращается к максимальному значению и продолжается оттуда.
Вы можете использовать методы Math#addExact()
и Math#subtractExact()
, которые выдадут ArithmeticException
при переполнении.
public static boolean willAdditionOverflow(int left, int right) {
try {
Math.addExact(left, right);
return false;
} catch (ArithmeticException e) {
return true;
}
}
public static boolean willSubtractionOverflow(int left, int right) {
try {
Math.subtractExact(left, right);
return false;
} catch (ArithmeticException e) {
return true;
}
}
Вы можете заменить int
на long
, чтобы выполнить те же проверки для long
.
Исходный код можно найти здесь и здесь соответственно.
Конечно, вы также могли бы просто использовать их сразу, вместо того чтобы прятать в boolean
служебном методе.
Если вы считаете, что это может происходить более чем часто, тогда рассмотрите возможность использования типа данных или объекта, который может хранить большие значения, например, long
или, возможно, java.math.BigInteger
. Последнее практически не переполняется, доступная память JVM является пределом.
Ответ 2
Что касается примитивных целочисленных типов, Java вообще не обрабатывает избыточный / недостаточный поток (для float и double поведение отличается, оно будет сбрасываться до + /- бесконечности, как предписывает IEEE-754).
При добавлении двух значений int вы не получите никаких указаний на то, когда происходит переполнение. Простой метод проверки на переполнение - использовать следующий больший тип для фактического выполнения операции и проверить, находится ли результат по-прежнему в диапазоне для исходного типа:
public int addWithOverflowCheck(int a, int b) {
// the cast of a is required, to make the + work with long precision,
// if we just added (a + b) the addition would use int precision and
// the result would be cast to long afterwards!
long result = ((long) a) + b;
if (result > Integer.MAX_VALUE) {
throw new RuntimeException("Overflow occured");
} else if (result < Integer.MIN_VALUE) {
throw new RuntimeException("Underflow occured");
}
// at this point we can safely cast back to int, we checked before
// that the value will be withing int's limits
return (int) result;
}
Что бы вы сделали вместо предложений throw , зависит от требований вашего приложения (throw, flush to min / max или просто регистрируйте что угодно). Если вы хотите обнаружить переполнение при длительных операциях, вам не повезло с примитивами, используйте вместо этого BigInteger .
Редактировать (2014-05-21): Поскольку на этот вопрос, похоже, ссылаются довольно часто, и мне самому приходилось решать ту же проблему, довольно легко оценить условие переполнения тем же методом, которым центральный процессор вычисляет свой флаг V.
По сути, это логическое выражение, которое включает знак обоих операндов, а также результат:
/**
* Add two int's with overflow detection (r = s + d)
*/
public static int add(final int s, final int d) throws ArithmeticException {
int r = s + d;
if (((s & d & ~r) | (~s & ~d & r)) < 0)
throw new ArithmeticException("int overflow add(" + s + ", " + d + ")");
return r;
}
В Java проще применить выражение (в if) ко всем 32 битам и проверить результат, используя < 0 (это эффективно проверит знаковый бит). Принцип работает точно так же для всех целочисленных примитивных типов, изменение всех объявлений в приведенном выше методе на long заставляет его работать долго.
Для меньших типов из-за неявного преобразования в int (подробнее см. JLS о побитовых операциях) вместо проверки < 0 при проверке необходимо явно замаскировать знаковый бит (0x8000 для коротких операндов, 0x80 для байтовых операндов, соответствующим образом настроить приведение и объявление параметров):
/**
* Subtract two short's with overflow detection (r = d - s)
*/
public static short sub(final short d, final short s) throws ArithmeticException {
int r = d - s;
if ((((~s & d & ~r) | (s & ~d & r)) & 0x8000) != 0)
throw new ArithmeticException("short overflow sub(" + s + ", " + d + ")");
return (short) r;
}
(Обратите внимание, что в приведенном выше примере используется выражение need for subtract для обнаружения переполнения)
Итак, как / почему работают эти логические выражения? Во-первых, некоторое логическое мышление показывает, что переполнение может только произойти, если знаки обоих аргументов одинаковы. Потому что, если один аргумент отрицательный, а другой положительный, результат (add) должен быть ближе к нулю, или, в крайнем случае, один аргумент равен нулю, такой же, как и другой аргумент. Поскольку аргументы сами по себе не могут создать условие переполнения, их сумма также не может создать переполнение.
Итак, что произойдет, если оба аргумента будут иметь одинаковый знак? Давайте рассмотрим случай, когда оба являются положительными: добавление двух аргументов, которые создают сумму, большую, чем типы MAX_VALUE , всегда приведет к отрицательному значению, поэтому происходит переполнение, если arg1 + arg2 > MAX_VALUE . Теперь максимальное значение, которое может получиться, будет MAX_VALUE + MAX_VALUE (в крайнем случае оба аргумента равны MAX_VALUE) . Для байта (пример) это будет означать 127 + 127 = 254. Просматривая битовые представления всех значений, которые могут возникнуть в результате добавления двух положительных значений, обнаруживается, что для всех тех, которые переполняются (от 128 до 254), установлен бит 7, в то время как для всех, которые не переполняются (от 0 до 127), бит 7 (самый верхний, знак) очищен. Это именно то, что проверяет первая (правая) часть выражения:
if (((s & d & ~r) | (~s & ~d & r)) < 0)
(~ s & ~ d & r) становится истинным, только если оба операнда (s, d) положительны, а результат (r) отрицателен (выражение работает со всеми 32 битами, но единственный бит, который нас интересует, - это самый верхний (знаковый) бит, который проверяется с помощью < 0).
Теперь, если оба аргумента отрицательны, их сумма никогда не может быть ближе к нулю, чем любой из аргументов, сумма должна быть ближе к минус бесконечности. Самое крайнее значение, которое мы можем получить, - это MIN_VALUE + MIN_VALUE , которое (опять же для примера с байтами) показывает, что для любого значения в диапазоне (от -1 до -128) установлен знаковый бит, в то время как для любого возможного значения с превышением (от-129 до -256) знаковый бит очищен. Итак, знак результата снова показывает условие переполнения. Это то, что проверяет левая половина (s & d & ~ r) для случая, когда оба аргумента (s, d) отрицательны, а результат положительный. Логика в значительной степени эквивалентна положительному случаю; у всех битовых шаблонов, которые могут возникнуть в результате добавления двух отрицательных значений, знаковый бит будет очищен тогда и только тогда, когда произошел неполный поток.
Ответ 3
По умолчанию Java int и long math автоматически переносятся при переполнении и переполнении. (Целочисленные операции над другими целочисленными типами выполняются путем предварительного преобразования операндов в int или long, согласно JLS 4.2.2.)
Начиная с Java 8, java.lang.Math
предоставляет addExact
, subtractExact
, multiplyExact
, incrementExact
decrementExact
и negateExact
статические методы как для аргументов int, так и для аргументов long, которые выполняют именованную операцию, вызывая ArithmeticException при переполнении. (Метода divideExact нет - вам придется проверить один особый случай (MIN_VALUE / -1
) самостоятельно.)
Начиная с Java 8, java.lang.Math также предоставляет toIntExact
преобразование long в int, вызывая ArithmeticException, если значение long не помещается в int. Это может быть полезно, например, для вычисления суммы целых чисел с использованием непроверенной длинной математики, затем с помощью toIntExact
для преобразования в int в конце (но будьте осторожны, чтобы ваша сумма не переполнялась).
Если вы все еще используете более старую версию Java, Google Guava предоставляет статические методы IntMath и LongMath для проверяемого сложения, вычитания, умножения и возведения в степень (с использованием функции overflow). Эти классы также предоставляют методы для вычисления факториалов и биномиальных коэффициентов, которые возвращают MAX_VALUE
при переполнении (что менее удобно проверять). Примитивные служебные классы Guava, SignedBytes
, UnsignedBytes
, Shorts
и Ints
, предоставляют checkedCast
методы для сужения более крупных типов (исключение IllegalArgumentException при under / overflow, не ArithmeticException ), а также saturatingCast
методы, которые возвращают MIN_VALUE
or MAX_VALUE
при переполнении.
Ответ 4
Java ничего не делает с целочисленным переполнением ни для int, ни для примитивных типов long и игнорирует переполнение положительными и отрицательными целыми числами.
В этом ответе сначала описывается целочисленное переполнение, приводится пример того, как это может произойти, даже с промежуточными значениями при вычислении выражения, а затем даются ссылки на ресурсы, которые предоставляют подробные методы предотвращения и обнаружения целочисленного переполнения.
Целочисленная арифметика и выражения, приводящие к неожиданному или необнаруженному переполнению, являются распространенной ошибкой программирования. Неожиданное или необнаруженное целочисленное переполнение также является хорошо известной эксплуатируемой проблемой безопасности, особенно поскольку оно влияет на объекты array, stack и list .
Переполнение может происходить как в положительном, так и в отрицательном направлении, когда положительное или отрицательное значение будет превышать максимальное или минимальное значение для рассматриваемого примитивного типа. Переполнение может произойти в промежуточном значении во время вычисления выражения или операции и повлиять на результат выражения или операции, где конечное значение, как ожидается, будет находиться в пределах диапазона.
Иногда отрицательное переполнение ошибочно называется неполным потоком. Неполный поток - это то, что происходит, когда значение ближе к нулю, чем позволяет представление. Неполный поток происходит в целочисленной арифметике и является ожидаемым. Целочисленный неполный поток происходит, когда целочисленное вычисление будет между -1 и 0 или 0 и 1. То, что было бы дробным результатом, усекается до 0. Это нормально и ожидается при целочисленной арифметике и не считается ошибкой. Однако это может привести к тому, что код выдаст исключение. Одним из примеров является исключение "ArithmeticException: / by zero", если результат целочисленного неполного потока используется в качестве делителя в выражении.
Рассмотрим следующий код:
int bigValue = Integer.MAX_VALUE;
int x = bigValue * 2 / 5;
int y = bigValue / x;
в результате x присваивается значение 0, а последующее вычисление bigValue / x выдает исключение "ArithmeticException: / на ноль" (т. Е. Деление на ноль), вместо того, чтобы y присваивается значение 2.
Ожидаемый результат для x будет 858,993,458, что меньше максимального значения int, равного 2,147,483,647. Однако промежуточный результат от вычисления Integer .MAX_Value * 2, будет равно 4,294,967,294, что превышает максимальное значение int и равно -2 в соответствии с 2s дополняющими целочисленными представлениями. Последующее вычисление -2 / 5 принимает значение 0, которое присваивается x.
Переставляя выражение для вычисления x в выражение, которое при вычислении делится перед умножением, следующий код:
int bigValue = Integer.MAX_VALUE;
int x = bigValue / 5 * 2;
int y = bigValue / x;
в результате x присваивается 858,993,458, а y присваивается 2, что и ожидалось.
Промежуточный результат из bigValue / 5 равен 429 496 729, что не превышает максимального значения для int. Последующее вычисление 429,496,729 * 2 не превышает максимального значения для int, и ожидаемый результат присваивается x . Тогда вычисление для y не делится на ноль. Оценки для x и y работают, как ожидалось.
Целочисленные значения Java хранятся как целочисленные представления со знаком дополнения 2s и ведут себя в соответствии с ними. Когда результирующее значение будет больше или меньше максимального или минимального целочисленного значения, вместо этого получается целочисленное значение дополнения 2. В ситуациях, явно не предназначенных для использования поведения дополнения 2s, которые являются наиболее обычными ситуациями целочисленной арифметики, результирующее значение дополнения 2s вызовет логическую ошибку программирования или вычисления, как было показано в примере выше. Отличная статья в Википедии описывает двоичные целые числа, дополняющие двоичные числа: Дополнение двойки - Википедия
Существуют методы, позволяющие избежать непреднамеренного целочисленного переполнения. Технологии могут быть классифицированы как использование тестирования предварительных условий, апкастинга и BigInteger.
Предварительное тестирование условий включает проверку значений, входящих в арифметическую операцию или выражение, чтобы гарантировать, что с этими значениями не произойдет переполнения. Программированию и дизайну потребуется создать тестирование, гарантирующее, что входные значения не вызовут переполнения, а затем определить, что делать, если входные значения приведут к переполнению.
Апкастинг включает использование большего примитивного типа для выполнения арифметической операции или выражения, а затем определение того, выходит ли результирующее значение за пределы максимального или минимального значения для целого числа. Даже при повторной обработке все еще возможно, что значение или какое-то промежуточное значение в операции или выражении превысит максимальные или минимальные значения для типа повторной обработки и вызовет переполнение, которое также не будет обнаружено и приведет к неожиданным и нежелательным результатам. С помощью анализа или предварительных условий может оказаться возможным предотвратить переполнение с помощью повышающей передачи, когда предотвращение без повышения передачи невозможно или непрактично. Если рассматриваемые целые числа уже являются длинными примитивными типами, то преобразование вверх с примитивными типами в Java невозможно.
Метод BigInteger включает использование BigInteger для арифметической операции или выражения с использованием библиотечных методов, использующих BigInteger . BigInteger не переполняется. При необходимости он использует всю доступную память. Его арифметические методы обычно лишь немного менее эффективны, чем операции с целыми числами. Все еще возможно, что результат с использованием BigInteger может превышать максимальные или минимальные значения для целого числа, однако в арифметике, приводящей к результату, переполнения не произойдет. Программистам и проектировщикам все равно нужно будет определить, что делать, если результат BigInteger превышает максимальные или минимальные значения для желаемого примитивного типа результата, например, int или long .
Программа CERT Института программной инженерии Карнеги-Меллона и Oracle создали набор стандартов для безопасного программирования на Java. В стандарты включены методы предотвращения и обнаружения целочисленного переполнения. Стандарт опубликован в виде свободно доступного онлайн-ресурса здесь: Стандарт безопасного кодирования CERT Oracle для Java
Раздел стандарта, который описывает и содержит практические примеры методов кодирования для предотвращения или обнаружения целочисленного переполнения, находится здесь: NUM00-J. Обнаруживать или предотвращать целочисленное переполнение
Также доступны книжная форма и PDF-форма стандарта безопасного кодирования CERT Oracle для Java.