В Java что означает NaN? - PullRequest
       80

В Java что означает NaN?

96 голосов
/ 11 апреля 2010

У меня есть программа, которая пытается уменьшить double до желаемого числа. Я получаю вывод NaN.

Что означает NaN в Java?

Ответы [ 11 ]

1 голос

Пример минимального запуска

Первое, что вам нужно знать, это то, что концепция NaN реализована непосредственно на оборудовании ЦП.

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

Следовательно, концепция будет очень похожа во всехлюбой язык, включая Java, который просто генерирует код с плавающей запятой непосредственно в CPU.

Прежде чем продолжить, вы можете сначала прочитать следующие ответы, которые я написал:

Теперь для некоторых действий Java.Большинство интересующих функций, не относящихся к базовому языку, живут внутри java.lang.Float.

Nan.java

import java.lang.Float;
import java.lang.Math;

public class Nan {
    public static void main(String[] args) {
        // Generate some NaNs.
        float nan            = Float.NaN;
        float zero_div_zero  = 0.0f / 0.0f;
        float sqrt_negative  = (float)Math.sqrt(-1.0);
        float log_negative   = (float)Math.log(-1.0);
        float inf_minus_inf  = Float.POSITIVE_INFINITY - Float.POSITIVE_INFINITY;
        float inf_times_zero = Float.POSITIVE_INFINITY * 0.0f;
        float quiet_nan1     = Float.intBitsToFloat(0x7fc00001);
        float quiet_nan2     = Float.intBitsToFloat(0x7fc00002);
        float signaling_nan1 = Float.intBitsToFloat(0x7fa00001);
        float signaling_nan2 = Float.intBitsToFloat(0x7fa00002);
        float nan_minus      = -nan;

        // Generate some infinities.
        float positive_inf   = Float.POSITIVE_INFINITY;
        float negative_inf   = Float.NEGATIVE_INFINITY;
        float one_div_zero   = 1.0f / 0.0f;
        float log_zero       = (float)Math.log(0.0);

        // Double check that they are actually NaNs.
        assert  Float.isNaN(nan);
        assert  Float.isNaN(zero_div_zero);
        assert  Float.isNaN(sqrt_negative);
        assert  Float.isNaN(inf_minus_inf);
        assert  Float.isNaN(inf_times_zero);
        assert  Float.isNaN(quiet_nan1);
        assert  Float.isNaN(quiet_nan2);
        assert  Float.isNaN(signaling_nan1);
        assert  Float.isNaN(signaling_nan2);
        assert  Float.isNaN(nan_minus);
        assert  Float.isNaN(log_negative);

        // Double check that they are infinities.
        assert  Float.isInfinite(positive_inf);
        assert  Float.isInfinite(negative_inf);
        assert !Float.isNaN(positive_inf);
        assert !Float.isNaN(negative_inf);
        assert one_div_zero == positive_inf;
        assert log_zero == negative_inf;
            // Double check infinities.

        // See what they look like.
        System.out.printf("nan            0x%08x %f\n", Float.floatToRawIntBits(nan           ), nan           );
        System.out.printf("zero_div_zero  0x%08x %f\n", Float.floatToRawIntBits(zero_div_zero ), zero_div_zero );
        System.out.printf("sqrt_negative  0x%08x %f\n", Float.floatToRawIntBits(sqrt_negative ), sqrt_negative );
        System.out.printf("log_negative   0x%08x %f\n", Float.floatToRawIntBits(log_negative  ), log_negative  );
        System.out.printf("inf_minus_inf  0x%08x %f\n", Float.floatToRawIntBits(inf_minus_inf ), inf_minus_inf );
        System.out.printf("inf_times_zero 0x%08x %f\n", Float.floatToRawIntBits(inf_times_zero), inf_times_zero);
        System.out.printf("quiet_nan1     0x%08x %f\n", Float.floatToRawIntBits(quiet_nan1    ), quiet_nan1    );
        System.out.printf("quiet_nan2     0x%08x %f\n", Float.floatToRawIntBits(quiet_nan2    ), quiet_nan2    );
        System.out.printf("signaling_nan1 0x%08x %f\n", Float.floatToRawIntBits(signaling_nan1), signaling_nan1);
        System.out.printf("signaling_nan2 0x%08x %f\n", Float.floatToRawIntBits(signaling_nan2), signaling_nan2);
        System.out.printf("nan_minus      0x%08x %f\n", Float.floatToRawIntBits(nan_minus     ), nan_minus     );
        System.out.printf("positive_inf   0x%08x %f\n", Float.floatToRawIntBits(positive_inf  ), positive_inf  );
        System.out.printf("negative_inf   0x%08x %f\n", Float.floatToRawIntBits(negative_inf  ), negative_inf  );
        System.out.printf("one_div_zero   0x%08x %f\n", Float.floatToRawIntBits(one_div_zero  ), one_div_zero  );
        System.out.printf("log_zero       0x%08x %f\n", Float.floatToRawIntBits(log_zero      ), log_zero      );

        // NaN comparisons always fail.
        // Therefore, all tests that we will do afterwards will be just isNaN.
        assert !(1.0f < nan);
        assert !(1.0f == nan);
        assert !(1.0f > nan);
        assert !(nan == nan);

        // NaN propagate through most operations.
        assert Float.isNaN(nan + 1.0f);
        assert Float.isNaN(1.0f + nan);
        assert Float.isNaN(nan + nan);
        assert Float.isNaN(nan / 1.0f);
        assert Float.isNaN(1.0f / nan);
        assert Float.isNaN((float)Math.sqrt((double)nan));
    }
}

GitHub upstream .

Запуск с:

javac Nan.java && java -ea Nan

Вывод:

nan            0x7fc00000 NaN
zero_div_zero  0x7fc00000 NaN
sqrt_negative  0xffc00000 NaN
log_negative   0xffc00000 NaN
inf_minus_inf  0x7fc00000 NaN
inf_times_zero 0x7fc00000 NaN
quiet_nan1     0x7fc00001 NaN
quiet_nan2     0x7fc00002 NaN
signaling_nan1 0x7fa00001 NaN
signaling_nan2 0x7fa00002 NaN
nan_minus      0xffc00000 NaN
positive_inf   0x7f800000 Infinity
negative_inf   0xff800000 -Infinity
one_div_zero   0x7f800000 Infinity
log_zero       0xff800000 -Infinity

Итак, из этого мы узнаем несколько вещей:

  • странные плавающие операции, которые не дают никакого ощутимого результата, дают NaN:

    • 0.0f / 0.0f
    • sqrt(-1.0f)
    • log(-1.0f)

    генерирует NaN.

    В C на самом деле можно запросить сигналы для таких операций с помощью feenableexcept, чтобы обнаружить их, но я не думаю, что он выставленв Java: Почему целочисленное деление на ноль 1/0 дает ошибку, а с плавающей запятой 1 / 0.0 возвращает "Inf"?

  • странные операции, которые находятся на пределеплюс или минус бесконечность, однако дают + - бесконечность вместо NaN

    • 1.0f / 0.0f
    • log(0.0f)

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

  • , если NaN является входом операции с плавающей запятой, выход также имеет тенденцию быть NaN

  • есть несколько возможных значений NaN 0x7fc00000, 0x7fc00001, 0x7fc00002, хотя x86_64, кажется, генерирует только 0x7fc00000.

  • NaN и бесконечность имеют одинаковое двоичное представление.

    Давайте разберем несколько из них:

    nan          = 0x7fc00000 = 0 11111111 10000000000000000000000
    positive_inf = 0x7f800000 = 0 11111111 00000000000000000000000
    negative_inf = 0xff800000 = 1 11111111 00000000000000000000000
                                | |        |
                                | |        mantissa
                                | exponent
                                |
                                sign
    

    Исходя из этого, мы подтверждаем, что IEEE754 указывает:

    • обаУ NaN и бесконечности есть показатель степени == 255 (все единицы)
    • У бесконечностей есть мантисса == 0. Следовательно, есть только две возможные бесконечности: + и -, дифференцированные знаковым битом
    • NaN имеетmantissa! = 0. Таким образом, существует несколько возможностей, кроме мантиссы == 0, которая равна бесконечности
  • NaN могут быть как положительными, так и отрицательными (верхний бит), хотя этоне влияет на обычные операции

Проверено в Ubuntu 18.10 amd64, OpenJDK 1.8.0_191.

...