为什么打印这个值虽然是 NaN?

Why is this value printed although being NaN?

以下代码假定我们在 x86 兼容系统上并且 long double 映射到 x87 FPU 的 80 位格式。

#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(value1): true

isnan(value2): false

isnan(value3): false

value1: 3.3614005946481929011e-4764

value2: 9.7056260598879139386e-4764

value3: 6.3442254652397210376e-4764

此处 value1 被 387 及更高分类为“不受支持”,因为它具有非零指数且不是全一指数 — 它实际上是“不正常”的。并且 isnan 按预期工作:该值确实不是数字(尽管不完全是 NaN)。第二个值 value2 设置了整数位,并且也按预期工作:它不是 NaN。第三个是缺失整数位的值。

但不知何故,数字 value1value2 都打印出来了,并且值因缺少整数位而完全不同!这是为什么?我尝试过的所有其他方法,如 printfto_string 只给出 0.00000.

更奇怪的是,如果我用 value1 做任何算术运算,在随后的打印中我会得到 nan。考虑到这一点,operator<<(long double) 是如何设法实际打印除 nan 之外的任何内容的?它是否显式设置整数位,或者它可能解析数字而不是对其进行任何 FPU 运算? (假设 Linux 32 位上的 g++4.8)。

All other methods I tried, like printf and to_string give just 0.00000.

operator<<(long double) 实际做的是使用 locale 库中的 num_put<> class 来执行数字格式化,而后者又使用 printf-系列函数(参见 C++ 标准的第 27.7.3.6 和 22.4.2.2 节)。

根据设置,locale 用于 long double 的 printf 转换说明符可能是以下任何一个:%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";

value1: 0.00000000000000000000

value1: 3.36140059464819290106e-4764

value1: 0x4.3d1ac8f246f235200000p-15826

value1: 3.3614005946481929011e-4764

value1: 3.3614005946481929011e-4764


Taking this into account, how does operator<<(long double) even manage to actually print anything but nan? Does it explicitly set the integer bit, or maybe it parses the number instead of doing any FPU arithmetic on it?

它打印非标准化值。

printf() 使用的从二进制到十进制浮点表示的转换可以在没有任何 FPU 算法的情况下执行。您可以在 stdio-common/printf_fp.c 源文件中找到 glibc 实现。

我正在尝试这个:

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 评估时被强制转换为 double 而不是 long double 并且严格:

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