Окончательное, неканоническое изменение двойного значения NaN во время выполнения - PullRequest
5 голосов
/ 16 июня 2011

Я пишу код Java, который взаимодействует с R, где значения «NA» отличаются от значений NaN. NA указывает на то, что значение «статистически отсутствует», то есть оно не может быть собрано или недоступно по другим причинам.

class DoubleVector {
     public static final double NA = Double.longBitsToDouble(0x7ff0000000001954L);

     public static boolean isNA(double input) {
         return Double.doubleToRawLongBits(input) == Double.doubleToRawLongBits(NA);
     }

     /// ... 
}

Следующий модульный тест демонстрирует связь между NaN и NA и отлично работает на моем ноутбуке с Windows, но «isNA (NA) # 2» завершается ошибкой иногда на моей рабочей станции Ubuntu.

@Test
public void test() {

    assertFalse("isNA(NaN) #1", DoubleVector.isNA(DoubleVector.NaN));
    assertTrue("isNaN(NaN)", Double.isNaN(DoubleVector.NaN));
    assertTrue("isNaN(NA)", Double.isNaN(DoubleVector.NA));
    assertTrue("isNA(NA) #2", DoubleVector.isNA(DoubleVector.NA));
    assertFalse("isNA(NaN)", DoubleVector.isNA(DoubleVector.NaN));
}

Из отладки выясняется, что DoubleVector.NA заменяется на каноническое значение NaN 7ff8000000000000L, но трудно сказать, потому что его печать на стандартный вывод дает значения, отличные от отладчика.

Кроме того, тест не пройден, только если он выполняется после ряда других предыдущих тестов; если я запускаю этот тест в одиночку, он всегда проходит.

Это ошибка JVM? Побочный эффект оптимизации?

Тесты всегда проходят:

java version "1.6.0_24"
Java(TM) SE Runtime Environment (build 1.6.0_24-b07)
Java HotSpot(TM) Client VM (build 19.1-b02, mixed mode, sharing)

Тесты иногда не выполняются:

java version "1.6.0_24"
Java(TM) SE Runtime Environment (build 1.6.0_24-b07)
Java HotSpot(TM) 64-Bit Server VM (build 19.1-b02, mixed mode)

1 Ответ

6 голосов
/ 16 июня 2011

Вы находитесь здесь в очень опасной воде, одной из немногих областей, где поведение виртуальной машины Java не точно указано.

В соответствии со спецификацией JVM существует только "aЗначение NaN "в диапазоне double.Никакая арифметическая операция над двойниками не может различить два разных значения NaN.

В документации longBitsToDouble() есть следующее примечание:

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

Таким образом, при условии, что обработка значения double всегда будет сохранять специфичные NaN неповрежденное значение - опасная вещь.

Самое чистое решение будет хранить ваши данные в long и преобразовывать в double после проверки ваших специальныхзначение.Это, однако, приведет к весьма заметному снижению производительности.

Вы могли бы уйти, добавив флаг strictfp в затронутых местах.Это никоим образом не гарантирует , что это будет работать, но это (возможно) изменит то, как ваша JVM обрабатывает значения с плавающей запятой, и может просто быть необходимой подсказкой, которая помогает.Однако все еще не будет переносимым.

...