«1. Обзор
Деление на ноль — это операция, которая не имеет смысла в обычной арифметике и, следовательно, не определена. Однако в программировании, хотя это часто связано с ошибкой, это не всегда так.
В этой статье мы рассмотрим, что происходит, когда в Java-программе происходит деление на ноль.
В соответствии со спецификацией операции деления в Java мы можем идентифицировать два разных случая деления на ноль: целые числа и числа с плавающей запятой.
2. Целые числа
Во-первых, с целыми числами все довольно просто. Деление целого числа на ноль приведет к ArithmeticException:
assertThrows(ArithmeticException.class, () -> {
int result = 12 / 0;
});
assertThrows(ArithmeticException.class, () -> {
int result = 0 / 0;
});
3. Типы с плавающей запятой
assertDoesNotThrow(() -> {
float result = 12f / 0;
});
Однако при работе с числами с плавающей запятой исключение не будет выдано:
~ ~~ Для обработки подобных случаев в Java используются специальные числовые значения, которые могут представлять результаты такой операции: NaN, POSITIVE_INFINITY и NEGATIVE_INFINITY.
3.1. NaN
assertEquals(Float.NaN, 0f / 0);
assertEquals(Double.NaN, 0d / 0);
Начнем с деления нулевых значений с плавающей запятой на ноль:
В этих случаях результатом будет NaN (не число).
3.2. Бесконечность
assertEquals(Float.POSITIVE_INFINITY, 12f / 0);
assertEquals(Double.POSITIVE_INFINITY, 12d / 0);
assertEquals(Float.NEGATIVE_INFINITY, -12f / 0);
assertEquals(Double.NEGATIVE_INFINITY, -12d / 0);
Далее разделим некоторые ненулевые значения на ноль:
Как видим, результат БЕСКОНЕЧНОСТЬ, причем знак зависит от знака операндов.
assertEquals(Float.NEGATIVE_INFINITY, 12f / -0f);
assertEquals(Double.NEGATIVE_INFINITY, 12f / -0f);
Более того, мы также можем использовать понятие отрицательного нуля, чтобы получить NEGATIVE_INFINITY:
3.3. Представление памяти
Итак, почему целочисленное деление на ноль вызывает исключение, а деление на ноль с плавающей запятой — нет?
Давайте посмотрим на это с точки зрения представления памяти. Для целых чисел нет шаблона битов, который можно использовать для хранения результата такой операции, в то время как числа с плавающей запятой имеют такие значения, как NaN или INFINITY, которые можно использовать в подобных случаях.
Теперь давайте рассмотрим двоичное представление числа с плавающей запятой как SEEEEEEE EFFFFFFF FFFFFFFF FFFFFFFF с одним битом (S) для знака, 8 битами (E) для экспоненты и остальными (F) для мантиссы.
В каждом из трех значений NaN, POSITIVE_INFINITY и NEGATIVE_INFINITY все биты в экспоненциальной части установлены в 1.
assertEquals(Float.POSITIVE_INFINITY, Float.intBitsToFloat(0b01111111100000000000000000000000));
assertEquals(Float.NEGATIVE_INFINITY, Float.intBitsToFloat(0b11111111100000000000000000000000));
assertEquals(Float.NaN, Float.intBitsToFloat(0b11111111100000010000000000000000));
assertEquals(Float.NaN, Float.intBitsToFloat(0b11111111100000011000000000100000));
В INFINITY все биты мантиссы установлены в 0, а в NaN мантисса не равна нулю: ~ ~~
4. Резюме
Подводя итог, в этой статье мы увидели, как деление на ноль работает в Java.
Такие значения, как INFINITY и NaN, доступны для чисел с плавающей запятой, но не для целых чисел. В результате деление целого числа на ноль приведет к исключению. Однако для float или double Java разрешает операцию.