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

Являются ли битовые шаблоны NaN аппаратно зависимыми?

Я читал о значениях NaN с плавающей запятой в спецификации языка Java (мне скучно). 32-разрядный float имеет этот бит-формат:

seee eeee emmm mmmm mmmm mmmm mmmm mmmm

s - знаковый бит, e - биты экспоненты, а m - бит мантиссы. Значение NaN кодируется как показатель всех 1s, а биты мантиссы - не все 0 (что равно +/- бесконечности). Это означает, что существует множество различных возможных значений NaN (имеющих разные значения бит s и m).

В этом случае JLS §4.2.3 говорится:

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

Текст в JLS, по-видимому, подразумевает, что результат, например, 0.0/0.0, имеет аппаратно-зависимый битовый шаблон и в зависимости от того, было ли это выражение вычислено как константа времени компиляции, аппаратное обеспечение зависит от него возможно, это аппаратное обеспечение, скомпилированное программой Java, или аппаратное обеспечение, на котором была запущена программа. Это все кажется очень шелушащимся, если это правда.

Я проверил следующий тест:

System.out.println(Integer.toHexString(Float.floatToRawIntBits(0.0f/0.0f)));
System.out.println(Integer.toHexString(Float.floatToRawIntBits(Float.NaN)));
System.out.println(Long.toHexString(Double.doubleToRawLongBits(0.0d/0.0d)));
System.out.println(Long.toHexString(Double.doubleToRawLongBits(Double.NaN)));

Выход на моей машине:

7fc00000
7fc00000
7ff8000000000000
7ff8000000000000

Результат не показывает ничего из ожидаемого. Биты экспоненты все 1. Верхний бит мантиссы также равен 1, что для NaN, по-видимому, указывает на "спокойное NaN", а не на "сигнализацию NaN" (https://en.wikipedia.org/wiki/NaN#Floating_point). Битовый знак и остальные биты мантиссы равны 0. Вывод также показывает, что не было никакой разницы между NaN, сгенерированными на моей машине, и постоянными NaN из классов Float и Double.

Мой вопрос: - это результат, гарантированный на Java, независимо от процессора компилятора или VM, или все это действительно непредсказуемо? JLS таинственна в этом.

Если этот результат гарантирован для 0.0/0.0, существуют ли какие-либо арифметические способы создания NaN, у которых есть другие (возможно, зависящие от оборудования?) битовые шаблоны? (Я знаю, что intBitsToFloat/longBitsToDouble может кодировать другие NaN, но я хотел бы знать, могут ли другие значения возникать из обычной арифметики.)


Следующий пункт: я заметил, что Float.NaN и Double.NaN указывают их точную битовую структуру, но в источнике (Float, Double), они генерируются 0.0/0.0. Если результат этого разделения действительно зависит от аппаратного обеспечения компилятора, кажется, что существует недостаток в спецификации или реализации.

4b9b3361

Ответ 1

Это то, что §2.3.2 спецификации JVM 7 должно сказать об этом:

Элементы набора двойных значений - это точно значения, которые могут быть представлены используя двойной формат с плавающей запятой, определенный в стандарте IEEE 754, за исключением что существует только одно значение NaN (IEEE 754 определяет 2 53 -2 различных значений NaN).

и §2.8.1:

Виртуальная машина Java не имеет значения NaN сигнализации.

Так технически существует только одно NaN. Но §4.2.3 JLS также говорит (сразу после цитаты):

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

Однако в версии 1.3 платформы Java SE были введены методы, позволяющие программисту различать значения NaN: методы Float.floatToRawIntBits и Double.doubleToRawLongBits. Для получения дополнительной информации заинтересованный читатель ссылается на спецификации для классов Float и Double.

Что я подразумеваю, чтобы точно указать, что вы и CandiedOrange предлагаете: он зависит от основного процессора, но Java относится к ним одинаково.

Но это становится лучше: по-видимому, вполне возможно, что ваши значения NaN беззвучно преобразуются в разные NaN, как описано в Double.longBitsToDouble():

Обратите внимание, что этот метод, возможно, не сможет вернуть двойной NaN с точно такой же битовой строкой, что и длинный аргумент. IEEE 754 различает два типа NaN, тихие NaN и сигнальные NaN. Различия между двумя типами NaN обычно не видны в Java. Арифметические операции с сигналами NaN превращают их в спокойные NaN с другой, но часто подобной битовой диаграммой. Тем не менее, на некоторых процессорах просто копирование сигнализации NaN также выполняет это преобразование. В частности, копирование сигнализации NaN, чтобы вернуть ее вызывающему методу, может выполнить это преобразование. Таким образом, longBitsToDouble может не иметь возможности вернуть double с сигнальной битовой диаграммой NaN. Следовательно, для некоторых длинных значений doubleToRawLongBits (longBitsToDouble (start)) может не совпадать с запуском. Более того, какие конкретные битовые шаблоны представляют собой сигнальные NaN, зависят от платформы; хотя все битовые диаграммы NaN, тихие или сигнальные, должны находиться в диапазоне NaN, указанном выше.

Для справки есть таблица аппаратно-зависимых NaN здесь. Вкратце:

- x86:     
   quiet:      Sign=0  Exp=0x7ff  Frac=0x80000
   signalling: Sign=0  Exp=0x7ff  Frac=0x40000
- PA-RISC:               
   quiet:      Sign=0  Exp=0x7ff  Frac=0x40000
   signalling: Sign=0  Exp=0x7ff  Frac=0x80000
- Power:
   quiet:      Sign=0  Exp=0x7ff  Frac=0x80000
   signalling: Sign=0  Exp=0x7ff  Frac=0x5555555500055555
- Alpha:
   quiet:      Sign=0  Exp=0      Frac=0xfff8000000000000
   signalling: Sign=1  Exp=0x2aa  Frac=0x7ff5555500055555

Итак, чтобы убедиться в этом, вам действительно понадобится один из этих процессоров и попробуйте его. Также приветствуются любые идеи о том, как интерпретировать более длинные значения для архитектуры Power и Alpha.

Ответ 2

Как я читал JLS здесь точное значение бит NaN зависит от того, кто/что сделал это, и поскольку JVM этого не сделал, не спрашивайте их. Вы также можете спросить их, что означает строка "Код ошибки 4".

Аппаратное обеспечение создает различные битовые шаблоны, предназначенные для представления различных видов NaN. К сожалению, различные виды оборудования производят разные битовые шаблоны для одних и тех же видов NaN. К счастью, существует стандартный шаблон, который Java может использовать, по крайней мере, для того, чтобы сказать, что это какой-то NaN.

Это похоже на то, что Java просматривает строку "Код ошибки 4" и говорит: "Мы не знаем, что означает" код 4 "на вашем оборудовании, но в этой строке было слово" ошибка ", поэтому мы думаем, что это ошибка."

JLS пытается дать вам шанс разобраться в этом, хотя:

"Однако в версии 1.3 платформы Java SE введены методы, позволяющие программисту различать значения NaN: методы Float.floatToRawIntBits и Double.doubleToRawLongBits. Заинтересованный читатель ссылается на спецификации для Float и Double для получения дополнительной информации.

Который смотрит на меня как на С++ reinterpret_cast. Это Java дает вам возможность проанализировать NaN самостоятельно, если вам известно, как кодируется его сигнал. Если вы хотите отследить аппаратные спецификации, чтобы вы могли предсказать, какие разные события должны создавать какие-либо битовые шаблоны NaN, которые вы можете сделать, но вы не в пределах единообразия, которые JVM должна была дать нам. Поэтому ожидайте, что он может измениться с аппаратного на аппаратное.

При тестировании, если число NaN, мы проверяем, совпадает ли оно с самим собой, поскольку это единственный номер, который не является. Это не означает, что бит отличается. Прежде чем сравнивать бит, JVM-тесты для множества битовых шаблонов, которые говорят, что это NaN. Если это какой-либо из этих паттернов, то он сообщает, что он не равен, даже если биты двух операндов на самом деле одинаковы (и даже если они разные).

Еще в 1964 году, когда нажимается дать точное определение порнографии, Верховный суд США судья Стюарт лихо сказал: "Я знаю, что когда я вижу это". Я думаю, что Java делает то же самое с NaN. Java не может сказать вам ничего, что сигнализация "NaN" может сигнализировать, потому что она не знает, как этот сигнал был закодирован. Но он может смотреть на биты и сказать, что это NaN, так как эта модель соответствует одному стандарту.

Если вы оказались на аппаратном обеспечении, которое кодирует все NaN с одинаковыми битами, вы никогда не докажете, что Java делает что-то, чтобы сделать NaN едиными битами. Опять же, как я читал JLS, они откровенно говорят, что вы здесь по своему усмотрению.

Я понимаю, почему это кажется шелушащимся. Это шелушащийся. Это просто не ошибка Java. Я расскажу о том, что некоторые из тех, в которых некоторые предприимчивые аппаратные производители придумали чудесно выразительные сигнальные шаблоны NaN, но они не смогли получить его в качестве стандарта, достаточно широко, чтобы Java мог рассчитывать на него. Это что-то шелушение. У нас есть все эти биты, зарезервированные для того, чтобы сигнализировать, что у нас есть NaN, и не могут их использовать, потому что мы не согласны с тем, что они означают. Наличие Java избило NaN в единое значение после того, как аппаратное обеспечение их уничтожит только для уничтожения этой информации, вредной производительности, и единственный выигрыш не будет выглядеть шелушащимся. Учитывая ситуацию, я рад, что они поняли, что могут обмануть свой выход из проблемы и определить NaN как ничто иное.

Ответ 3

Вот программа, демонстрирующая разные битовые шаблоны NaN:

public class Test {
  public static void main(String[] arg) {
    double myNaN = Double.longBitsToDouble(0x7ff1234512345678L);
    System.out.println(Double.isNaN(myNaN));
    System.out.println(Long.toHexString(Double.doubleToRawLongBits(myNaN)));
    final double zeroDivNaN = 0.0 / 0.0;
    System.out.println(Double.isNaN(zeroDivNaN));
    System.out
        .println(Long.toHexString(Double.doubleToRawLongBits(zeroDivNaN)));
  }
}

выход:

true
7ff1234512345678
true
7ff8000000000000

Независимо от того, что делает оборудование, программа сама может создавать NaN, которые могут быть не такими, как, например, 0.0/0.0 и может иметь некоторый смысл в программе.

Ответ 4

Единственное другое значение NaN, которое я мог бы генерировать с помощью обычных арифметических операций, остается неизменным, но с измененным знаком:

public static void main(String []args) {
    Double tentative1 = 0d/0d;
    Double tentative2 = Math.sqrt(-1d);

    System.out.println(tentative1);
    System.out.println(tentative2);

    System.out.println(Long.toHexString(Double.doubleToRawLongBits(tentative1)));
    System.out.println(Long.toHexString(Double.doubleToRawLongBits(tentative2)));

    System.out.println(tentative1 == tentative2);
    System.out.println(tentative1.equals(tentative2));
}

Вывод:

NaN

NaN

7ff8000000000000

fff8000000000000

ложно

True