Почему это значение напечатано, хотя оно является NaN?

В следующем коде предполагается, что мы находимся в x86-совместимой системе и что long double отображается в 80-битный формат x87 FPU.

#include <cmath>
#include <array>
#include <cstring>
#include <iomanip>
#include <iostream>

int main()
{
    std::array<uint8_t,10> data1{0x52,0x23,0x6f,0x24,0x8f,0xac,0xd1,0x43,0x30,0x02};
    std::array<uint8_t,10> data2{0x52,0x23,0x6f,0x24,0x8f,0xac,0xd1,0xc3,0x30,0x02};
    std::array<uint8_t,10> data3{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x30,0x02};
    long double value1, value2, value3;
    static_assert(sizeof value1 >= 10,"Expected float80");
    std::memcpy(&value1, data1.data(),sizeof value1);
    std::memcpy(&value2, data2.data(),sizeof value2);
    std::memcpy(&value3, data3.data(),sizeof value3);
    std::cout << "isnan(value1): " << std::boolalpha << std::isnan(value1) << "\n";
    std::cout << "isnan(value2): " << std::boolalpha << std::isnan(value2) << "\n";
    std::cout << "isnan(value3): " << std::boolalpha << std::isnan(value3) << "\n";
    std::cout << "value1: " << std::setprecision(20) << value1 << "\n";
    std::cout << "value2: " << std::setprecision(20) << value2 << "\n";
    std::cout << "value3: " << std::setprecision(20) << value3 << "\n";
}

Вывод:

isnan (значение1): true

isnan (значение2): false

isnan (значение3): false

значение1: 3.3614005946481929011e-4764

значение2: 9.7056260598879139386e-4764

значение3: 6.3442254652397210376e-4764

Здесь value1 классифицируется как "неподдерживаемый" на 387 и выше, потому что он имеет ненулевой и не все-показательный показатель - он на самом деле "ненормальный". И isnan работает так, как ожидалось, с ним: значение действительно не имеет никакого числа (хотя и не точно NaN). Второе значение value2 имеет этот битовый набор, а также работает как ожидалось: это не NaN. Третий - это значение отсутствующего целочисленного бита.

Но так или иначе отображаются как цифры value1, так и value2, и значения в точности отличаются от отсутствующего целочисленного бита! Почему это? Все другие методы, которые я пробовал, например printf и to_string, дают только 0.00000.

Даже незнакомец, если я делаю арифметику с value1, в последующих отпечатках я получаю nan. Принимая это во внимание, как operator<<(long double) удается даже напечатать что-либо, кроме nan? Является ли это явным образом заданием целочисленного бита, или, может быть, он анализирует число вместо того, чтобы выполнять какую-либо арифметику FPU? (предполагая g++ 4.8 на Linux 32 бит).

Ответ 1

Все другие методы, которые я пробовал, например printf и to_string, дают только 0.00000.

В действительности operator<<(long double) использует класс num_put<> из библиотеки locale для выполнения числового форматирования, который, в свою очередь, использует одну из функций printf -family (см. разделы 27.7.3.6 и 22.4.2.2 стандарта С++).

В зависимости от настроек спецификатор преобразования printf, используемый для long double на locale, может быть любым: %Lf, %Le, %Le, %La, %La, %Lg или %Lg.

В вашем (и моем) случае это выглядит как %Lg:

printf("value1: %.20Lf\n", value1);
printf("value1: %.20Le\n", value1);
printf("value1: %.20La\n", value1);
printf("value1: %.20Lg\n", value1);
std::cout << "value1: " << std::setprecision(20) << value1 << "\n";

значение1: 0,00000000000000000000

значение1: 3.36140059464819290106e-4764

value1: 0x4.3d1ac8f246f235200000p-15826

значение1: 3.3614005946481929011e-4764

значение1: 3.3614005946481929011e-4764


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

Он печатает ненормализованное значение.

Преобразование из двоичного в десятичное представление с плавающей запятой, используемое printf(), может выполняться без арифметики FPU. Вы можете найти реализацию glibc в исходном файле stdio-common/printf_fp.c.

Ответ 2

Я пытался это сделать:

long double value = std::numeric_limits<long double>::quiet_NaN();
std::cout << "isnan(value): " << std::boolalpha << std::isnan(value) << "\n";
std::cout << "value: " << std::setprecision(20) << value << "\n";

Итак, мое предположение заключается в том, что, как указано здесь: http://en.cppreference.com/w/cpp/numeric/math/isnan значение заносится в двойное и недолгое удвоение при оценке std::isnan и строго:

std::numeric_limits<long double>::quiet_NaN() != std::numeric_limits<double>::quiet_NaN()