Различия в NAN - стандартное представление :: nan и quiet_NaN () против макроса NAN - PullRequest
2 голосов
/ 16 апреля 2020

Мне было интересно, каковы различия между следующими типами nan. За исключением визуальной разницы NAN_macro (которая оценивается как -nan(ind), а не просто nan), все они, кажется, ведут себя одинаково (как в примере ниже).

У меня был посмотрите на некоторые другие ответы, например В чем разница между тихим NaN и сигнальным NaN? . Но я до сих пор не совсем понимаю, почему NAN равно -nan(ind), тогда как std::numeric_limits<double>::quiet_NaN() равно nan, или почему у нас есть std::nan("") и std::nan("1"), если в конце дня они оба кажутся одинаковыми concept.

Любые объяснения или уточняющие ссылки были бы великолепны.

#include <cmath>
#include <limits>

int main()
{
    using num_lim = std::numeric_limits<double>;

    const double NAN_macro   = static_cast<double>(NAN);  // -nan(ind)
    const double NAN_quiet   = num_lim::quiet_NaN();      // nan
    const double NAN_sig     = num_lim::signaling_NaN();  // nan
    const double NAN_str     = std::nan("");              // nan
    const double NAN_str1    = std::nan("1");             // nan
    const double NAN_strLong = std::nan("some string");   // nan

    const bool isnan_macro   = std::isnan(NAN_macro);    // true
    const bool isnan_quiet   = std::isnan(NAN_quiet);    // true
    const bool isnan_sig     = std::isnan(NAN_sig);      // true
    const bool isnan_str     = std::isnan(NAN_str);      // true
    const bool isnan_str1    = std::isnan(NAN_str1);     // true
    const bool isnan_strLong = std::isnan(NAN_strLong);  // true

    const bool not_equal_macro   = (NAN_macro   != NAN_macro);   // true
    const bool not_equal_quiet   = (NAN_quiet   != NAN_quiet);   // true
    const bool not_equal_sig     = (NAN_sig     != NAN_sig);     // true
    const bool not_equal_str     = (NAN_str     != NAN_str);     // true
    const bool not_equal_str1    = (NAN_str1    != NAN_str1);    // true
    const bool not_equal_strLong = (NAN_strLong != NAN_strLong); // true

    const double sum_macro   = 123.456 + NAN_macro;    // -nan(ind)
    const double sum_quiet   = 123.456 + NAN_quiet;    // nan
    const double sum_sig     = 123.456 + NAN_sig;      // nan
    const double sum_str     = 123.456 + NAN_str;      // nan
    const double sum_str1    = 123.456 + NAN_str1;     // nan
    const double sum_strLong = 123.456 + NAN_strLong;  // nan
}

1 Ответ

2 голосов
/ 20 апреля 2020

Стандарт IEEE 754 представляет NaN в виде битовой комбинации с показателем степени 1 и ненулевой дробью (обратите внимание, что все десятичные значения с плавающей запятой представлены знаком, показателем степени и дробь "), то есть много разных NaN, которые вы можете представить, поскольку" ненулевая дробь "может быть множеством разных значений.

Чтобы выделить ваши NaN представления, расширьте свой код с помощью это:

#include <cmath>
#include <limits>
#include <bitset>
#include <iostream>

union udouble {
  double d;
  unsigned long long u;
};

void Display(double doubleValue, char* what)
{
    udouble ud;
    ud.d = doubleValue;
    std::bitset<sizeof(double) * 8> b(ud.u);
    std::cout << "BitSet : " << b.to_string() << " for " << what << std::endl;
}

int main()
{
    using num_lim = std::numeric_limits<double>;

    const double NAN_macro   = static_cast<double>(NAN);  // -nan(ind)
    const double NAN_quiet   = num_lim::quiet_NaN();      // nan
    const double NAN_sig     = num_lim::signaling_NaN();  // nan
    const double NAN_str     = std::nan("");              // nan
    const double NAN_str1    = std::nan("1");             // nan
    const double NAN_strLong = std::nan("some string");   // nan

    ...

    Display( NAN_macro, "NAN_macro" );
    Display( NAN_quiet, "NAN_quiet" );
    Display( NAN_sig, "NAN_sig" );
    Display( NAN_str, "NAN_str" );
    Display( NAN_str1, "NAN_str1" );
    Display( NAN_strLong, "NAN_strLong" );
}

Даже если у него есть некоторый UB (это можно исправить, см. комментарий Руслана), это работает, чтобы проиллюстрировать то, что я объясняю ниже), эта программа выводит:

BitSet : 0111111111111000000000000000000000000000000000000000000000000000 for NAN_macro
BitSet : 0111111111111000000000000000000000000000000000000000000000000000 for NAN_quiet
BitSet : 0111111111110100000000000000000000000000000000000000000000000000 for NAN_sig
BitSet : 0111111111111000000000000000000000000000000000000000000000000000 for NAN_str
BitSet : 0111111111111000000000000000000000000000000000000000000000000001 for NAN_str1
BitSet : 0111111111111000000000000000000000000000000000000000000000000000 for NAN_strLong

Все они имеют:

  • «0» в качестве знака (вы, вероятно, получите «1» для NAN_macro, так как вы сообщите об этом как -nan, я не знаю). Я использовал g ++, я уверен, что некоторые компиляторы могут определять макрос NAN по-разному, я сомневаюсь, что в любом случае это часть стандарта C ++.
  • "11111111111" как показатель степени
  • , тогда разные значения " дробь "... вообще-то, но никогда не только нули, иначе это больше не будет NaN.

На самом деле, это значение" дробь "или" полезная нагрузка "(см. комментарий Руслана) может использоваться чтобы хранить любую информацию, которую вы хотели бы сохранить (например, «почему здесь был нан»?), это называется NaN-бокс .

Именно поэтому в какой-то момент у вас могут быть "разные" значения NaN ( quiet_NaN , signaling_NaN или любые NaN, которые вы создаете, соответствуют стандарту IEEE 754 .. . даже если у вас разные представления памяти, все это значения NaN (x!=x и std::isnan(x)==true).

Итак, чтобы ответить на ваши вопросы:

почему NAN -nan(ind), тогда как std::numeric_limits<double>::quiet_NaN() - это nan

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

почему у нас std::nan("") и std::nan("1"), если в конце дня они оба будут казаться одинаковыми.

Может быть, "помочь вам сыграть с NaN-боксом" может быть ответом, даже если я сомневаюсь эти функции были созданы для этой конкретной цели c. Правильный ответ можно просто «позволить вам решить, какую« дробную »величину вы хотите использовать для NaN, если вам нужно что-то отличное от std::quiet_NaN и std::signaling_NaN"

Источники: https://steve.hollasch.net/cgindex/coding/ieeefloat.html

Также используется { ссылка } для вывода NaN представления памяти.

...