Подтвердить что ты не робот

Почему кастинг Double.NaN для int не генерирует исключение в Java?

Итак, я знаю, что IEEE 754 указывает некоторые специальные значения с плавающей запятой для значений, которые не являются действительными числами. В Java, приведение этих значений к примитиву int делает не исключение, как я ожидал. Вместо этого мы имеем следующее:

int n;
n = (int)Double.NaN; // n == 0
n = (int)Double.POSITIVE_INFINITY; // n == Integer.MAX_VALUE
n = (int)Double.NEGATIVE_INFINITY; // n == Integer.MIN_VALUE

В чем обоснование не бросания исключений в этих случаях? Является ли это стандартом IEEE, или это был просто выбор дизайнеров Java? Есть ли плохие последствия, о которых я не знаю, могут ли исключения с такими отбрасываниями?

4b9b3361

Ответ 1

В чем причина отказа от исключений в этих случаях?

Я полагаю, что причины включают в себя:

  • Это крайние случаи, и они редко встречаются в приложениях, которые делают подобные вещи.

  • Поведение не является "совершенно неожиданным".

  • Когда приложение преобразуется из двойного в целое, ожидается значительная потеря информации. Приложение либо будет игнорировать эту возможность, либо приведению будет предшествовать проверка, чтобы защититься от него... которая также может проверить эти случаи.

  • Никакие другие операции double/float не приводят к исключениям, и (IMO) было бы немного шизофренично делать это в этом случае.

  • Возможно, на некоторых аппаратных платформах (нынешних или будущих) может быть снижение производительности.

Является ли это стандартом IEEE, или это был просто выбор разработчиков Java?

Последнее, я думаю.

Есть ли плохие последствия, о которых я не знаю, если бы исключения были возможны с такими бросками?

Никто кроме очевидных...

(Но это не совсем актуально. Спецификации JLS и JVM говорят то, что они говорят, и их изменение может привести к поломке существующего кода. И сейчас речь идет не только о Java-коде...)


Я немного покопался. Многие инструкции x86, которые можно использовать для преобразования из двойных в целые, похоже, генерируют аппаратные прерывания... если не маскированы. Мне неясно, проще или сложнее реализовать указанное поведение Java, чем альтернатива, предложенная ФП.

Ответ 2

В чем причина не метания исключения в этих случаях? Это IEEE, или это просто выбор разработчиками Java?

IEEE 754-1985 Стандарт на страницах 20 и 21 в разделах 2.2.1 NAN и 2.2.2 Бесконечность ясно объясняет причины, по которым значения NAN и Infinity требуемый стандартом. Поэтому это не предмет Java.

Спецификация виртуальной машины Java в разделе 3.8.1 Арифметика с плавающей точкой и IEEE 754 гласит, что когда преобразования в интегральные типы, то JVM будет применять округление к нулю, что объясняет результаты, которые вы видите.

В стандарте упоминается функция с именем "обработчик ловушек", которая может использоваться для определения того, когда происходит переполнение или NAN, но спецификация виртуальной машины Java четко заявляет, что это не реализовано для Java. В разделе 3.8.1 говорится:

Операции с плавающей запятой Виртуальная машина Java не бросает исключения, ловушка или другой сигнал IEEE 754 исключительные условия недействительная операция, деление на ноль, переполнение, недостаточное или неточное. Виртуальная машина Java не имеет сигнализации NaN.

Таким образом, поведение не задано независимо от последствий.

Есть ли плохие последствия, которые я не знают, будут ли исключения возможно с такими отбрасываниями?

Понимание причин, указанных в стандарте, должно быть достаточным для ответа на этот вопрос. Стандарт объясняет с исчерпывающими примерами последствия, которые вы просите здесь. Я бы опубликовал их, но здесь будет слишком много информации, и примеры могут быть невозможно соответствующим образом отформатировать в этом редакторе.

ИЗМЕНИТЬ

Я читал последний обзор технического обслуживания Спецификации виртуальной машины Java, опубликованный недавно JCP в рамках их работы над JSR 924 и в разделе 2.11.14 с именем type conversion istructions содержится дополнительная информация, которая может помочь вам в поиске ответов, еще не то, что вы ищете, но я считаю, что это помогает. В нем говорится:

При сужении числового преобразования значение с плавающей запятой в интеграл тип T, где T либо int, либо long, значение с плавающей запятой преобразуется следующим образом:

  • Если значением с плавающей запятой является NaN, результатом преобразования является int или long 0.
  • В противном случае, если значение с плавающей запятой не бесконечно, значение с плавающей запятой округляется до целочисленное значение V с использованием IEEE 754
    в нулевом режиме.

Есть два случая:

  • Если T длинно, и это целочисленное значение может быть представлено как длинное, тогда результатом является длинное значение V.
  • Если T имеет тип int, и это целочисленное значение может быть представлено как int, то результатом является int значение V.

В противном случае:

  • Либо значение должно быть слишком маленьким (отрицательное значение большого величина или отрицательная бесконечность), и результат - наименьший представляемое значение типа int или долго.
  • Или значение должно быть слишком большим (положительное значение большой величины или положительной бесконечности), а результат является наибольшей представимой величиной тип int или long.

Сужение числового преобразования из двойное поплавок ведет себя в соответствии с IEEE 754. Результат корректен округляется с использованием IEEE 754 round до ближайший режим. Значение, слишком малое, чтобы быть представленный как float, преобразуется в положительный или отрицательный нуль типа плавать; слишком большое значение представленный как float, преобразуется в положительная или отрицательная бесконечность. двойной NaN всегда преобразуется в поплавок NaN.

Несмотря на то, что переполнение, недостаточность или потеря точности может произойти, сужение конверсий между числовые типы никогда не вызывают Виртуальная машина Java, чтобы исключение времени выполнения (не путать с плавающей точкой IEEE 754 исключение).

Я знаю, что это просто повторяет то, что вы уже знаете, но это ключ, кажется, что стандарт IEEE имеет требование округления до ближайшего. Возможно, вы найдете причины такого поведения.

ИЗМЕНИТЬ

Стандарт IEEE, о котором идет речь в разделе 2.3.2. Режимы округления. Состояния:

По по умолчанию, округление означает раунд к ближайший. Стандарт требует, чтобы три других быть предусмотрены режимы округления; а именно, округлить в сторону 0, круглый в сторону + бесконечности и вокруг по отношению к -Infinity.

При использовании с обращенным к целочисленным операции, круглый в стороне -Infinity приводит к тому, чтобы стать обращенной функцией пола, в то время, круглый к + бесконечностям на потолке.

режим округления влияет переполнение, потому что, когда круглый в сторону O или раунда в направлении -Infinite находится в силе, переполнение положительной величины приводит к результату по умолчанию, является наибольшим представляемое число, а не + бесконечность.

Аналогично, переполнение отрицательная величина будет производить самое большое отрицательное число, когда круглый в сторону + бесконечности или раунда в направлении O находится в силе.

Затем они приступают к упоминанию примера того, почему это полезно в интервальной арифметике. Не уверен, опять же, что это ответ, который вы ищете, но он может обогатить ваш поиск.

Ответ 3

На самом деле, я думаю, что во время какого-то кастинга выполняются бит-операции (возможно, для первоочередных задач?), так что вы можете иметь какое-то неожиданное поведение. Посмотрите, что произойдет, когда вы используете → и < операторы.

Например:

public static void main(String[] args) {
    short test1 = (short)Integer.MAX_VALUE;
    System.out.println(test1);
    short test2 = (short)Integer.MAX_VALUE-1;
    System.out.println(test2);
    short test3 = (short)Integer.MAX_VALUE-2;
    System.out.println(test3);
    short test4 = (short)Double.MAX_VALUE-3;
    System.out.println(test4);
}

выведет:

-1
-2
-3
-4

Ответ 4

С 1998 года есть презентация ACM, которая все еще кажется удивительно актуальной и приносит некоторый свет: https://people.eecs.berkeley.edu/~wkahan/JAVAhurt.pdf.

Более конкретно, относительно неожиданного отсутствия исключений при литье NaN и бесконечности: см. стр. 3, пункт 3: "Бесконечность и NaNs развязаны без защиты ловушек и флагов с плавающей запятой, предусмотренных стандартами IEEE 754/854 belie Javas Claim к надежности".

Презентация на самом деле не отвечает "почему", но объясняет последствия проблемных дизайнерских решений в реализации языка Java с плавающей запятой и ставит их в соответствие со стандартами IEEE и даже с другими реализациями.

Ответ 6

В этом отношении Java не работает, но ее исправление может привести к поломке существующего кода.

Попытка оправдать такое поведение, углубляясь в спецификации IEEE или Java, кажется мне извращенной. 1/0 выдает арифметическое исключение, Math.round(Float.NaN), очевидно, тоже должен.

(Это Принцип Наименьшего Удивления.)