Цель определения POSITIVE_INFINITY, NEGATIVE_INFINITY, констант NaN только для типов данных с плавающей точкой, но не для интегральных типов данных
Я хочу понять, почему константы POSITIVE_INFINITY
и NEGATIVE_INFINITY
определены только для типов данных с плавающей запятой (float
, double
и их обертки),
public static final float POSITIVE_INFINITY = 1.0f / 0.0f;
public static final float NEGATIVE_INFINITY = -1.0f / 0.0f;
но не для целых типов данных (byte
, short
, int
, long
и их обертки). Это влияет на результат операции деления на разные типы данных. Например:
Для интегральных типов:
int z = 10/0;
System.out.println(z);
Output:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at TesterClass.main(TesterClass.java:16)
Для типов с плавающей запятой:
double z = 10/0.0;
System.out.println(z);
Output:
Infinity
Ответы
Ответ 1
Целочисленные типы в Java используют либо двоичное без знака (для char
), либо двухзначное подписанное представление. Нет представления для "бесконечности" ни в одном из этих видов представлений. Например, с int
есть 2 ^ 32 возможных значений, и все они представляют собой конечные числа.
(Integer.MIN_VALUE
равно -2 31Integer.MAX_VALUE
равно 2 31 - 1, и если вы посчитаете их все... включая ноль..., то получите 2 32 различных значения.)
Напротив, числа с плавающей точкой представлены с использованием двоичных представлений IEEE с двоичными числами, и у них есть стандартный способ представления значений как бесконечности, так и не числа.
Поэтому имеет смысл определить константы POSITIVE_INFINITY
и NEGATIVE_INFINITY
для типов с плавающей запятой, а определить их для целочисленных типов невозможно.
Если вы хотите знать, почему это так:
-
Целочисленные представления были разработаны/выбраны (давно!), Чтобы максимизировать скорость. Любые особые случаи (например, значения, зарезервированные для представления бесконечности и т.д.) Могут сделать целочисленное арифметическое оборудование более сложным и медленным. Если целью разработчика оборудования является целочисленное сложение за один такт, то усложнение сложения означает, что тактовая частота должна быть ниже. Это влияет на скорость всего процессора.
Обратная сторона такова:
- Переполнение происходит без какого-либо явного уведомления (что может быть или не быть желательным)
- Деление на ноль должно осуществляться через аппаратное исключение, и это приводит к значительному снижению производительности... если это действительно происходит.
-
Комитет по стандартизации, который разрабатывал представления IEEE с плавающей точкой, также учитывал требования научных и инженерных областей, в которых необходимо было представлять бесконечность. Операции с плавающей запятой уже выполняются медленнее и сложнее из-за необходимости масштабирования и т.д. Поэтому они, скорее всего, уже являются многоцикловыми инструкциями, и, вероятно, имеется некоторая "слабость" для работы с особыми случаями.
Кроме того, есть преимущество, состоящее в том, что: значения INF и NaN позволяют операциям, которые их создают, выполняться без аппаратного исключения, но без "очистки плохих операций", как при целочисленном переполнении.
Обратите внимание, что два дополнения были использованы в работающем компьютере в 1949 году (EDSAC). Стандарт IEEE 754 появился в 1985 году.
Для чего это стоит, некоторые языки программирования знают о целочисленном переполнении; например Ада. Но они не делают этого с представлениями бесконечности и т.д. Вместо этого они генерируют исключение (или эквивалент), когда операция переполняется. Тем не менее, это добавляет снижение производительности, поскольку обнаружение переполнения обычно влечет за собой дополнительную инструкцию после каждой целочисленной арифметической инструкции для проверки бита состояния "переполнение". (То, как работают современные наборы инструкций...)
Ответ 2
Это часть стандарта IEEE 754 с плавающей запятой, как указано в эта спецификация:
Типы с плавающей запятой - это float
и double
, которые концептуально связаны с значениями и операциями IEEE 754 с одной и той же 32-разрядной и двойной точностью 64-битного формата, как указано в стандарте IEEE для двоичного плавающего -Point Арифметика, стандарт ANSI/IEEE 754-1985 (IEEE, Нью-Йорк).
Стандарт IEEE 754 включает не только положительные и отрицательные числа, состоящие из знака и величины, но и положительные и отрицательные нули, положительные и отрицательные бесконечности и специальные значения Not-a-Number ( в дальнейшем сокращенно NaN).
Эти специальные значения вычисляются на основе их битовых представлений в соответствии со стандартом. Например, положительная бесконечность double
вычисляется на основе представления бит 0x7ff0000000000000
.
Напротив, целые типы не имеют битового представления для бесконечных значений. Они имеют только представления для конечных чисел. Класс Integer
определяет минимальные и максимальные конечные значения как -2 31 и 2 31-1.
Ответ 3
Как указывали другие, это в спецификации IEEE и т.д. Поплавки и удваивания поддерживают NaN и Infinity, какие целые числа не имеют.
В терминах рассуждений позади, ничто не делится на ноль, а целыми числами вы знаете, что пытаетесь разделить на ноль.
Числа с плавающей запятой не являются точными. 0.003f - 0.001f - 0.002f математически ноль, но по спецификации IEEE и нашей способности представлять числа в компьютерах это -2.3283064E-10. Существует конечное число десятичных чисел, которые вы можете представить в двоичном формате, и нет никакого представления, которое позволило бы нам всегда получать правильное значение для нуля.
Если tinyFloat == (0.003f - 0.001f - 0.002f) == -2.3283064E-10
Это математически нулевое значение и практически равно нулю, но 1f/tinyFloat == -4.2949673E9
// This still works too:
scala> Integer.MAX_VALUE / (tinyFloat * tinyFloat * tinyFloat)
res58: Float = -1.7014118E38
// But eventually you overflow
scala> Integer.MAX_VALUE / (tinyFloat * tinyFloat * tinyFloat * tinyFloat)
res59: Float = Infinity
(Если вы не знакомы, Scala - это язык JVM, поэтому приведенные выше типы значений совпадают с Java. )
Этот последний tinyFloat ^ 4 по-прежнему не равен нулю, поэтому для компьютера не имеет смысла бросать исключение ArithmeticException. Эта проблема не существует с целыми числами. Нет другого способа переполнения с разделением. Integer.MAX_VALUE/1 по-прежнему Integer.MAX_VALUE. Вы либо делитесь на ноль, что является математически недействительным и представимым в двоичном формате, либо вы его не получили, и получили действительный результат.
Ответ 4
Математически, 1/0 = Infinity
, потому что мы рассматриваем деление на ноль как limit(x -> 0) 1/x
, кроме того, с неявным пониманием того, что x
является положительной величиной. Деление с плавающей запятой должно поддерживать деление на небольшие количества и учитывая, что операции семантически приходят с небольшим погрешностью и при условии, что эта операция может включать в себя 0, следует, что выбрасывание исключения из-за деления на небольшое количество, вызванное округлением чтобы точно равняться 0, совершенно неуместно.
Подразделение на целое число 0 действительно равно undefined. Если вы делите на целые ноль, вы, вероятно, не должны иметь. И производительность, конечно же, была бы серьезно скомпрометирована от выполнения этой дополнительной вещи.
Для сравнения Integer поддерживает Integer.MAX/MIN_VALUE
. Они не должны создаваться в результате операции, а работать с алгоритмами с такими шагами, как int minSoFar = Integer.MAX_VALUE;
и т.п.