为什么 printf 和 isnan 不同意 long double 值是否为 NaN?

vit*_*aut 5 c c++ floating-point clang

考虑以下代码:

#include <math.h>
#include <stdio.h>
#include <string.h>

int main() {
  __uint128_t n = (__uint128_t(0x00007ffff7dd6e65ULL) << 64) |
                               0x63696c400a2d2d21ULL;
  long double d = 0;
  memcpy(&d, &n, sizeof(long double));
  printf("%d\n", isnan(d));
  printf("%Le\n", d);
}
Run Code Online (Sandbox Code Playgroud)

当使用 clang 版本 12.0.0 (clang-1200.0.32.29) 编译并在 macOS 上运行时,它会产生以下输出:

1
3.345927e+3575
Run Code Online (Sandbox Code Playgroud)

为什么将其isnan报告long double为 NaN 而将其printf打印为3.345927e+3575

iostreams 和 clang++ 也是如此:

std::cout << d; // prints 3.34593e+3575
Run Code Online (Sandbox Code Playgroud)

具体来说,为什么在处理这个数字时不同的 C 和 C++ API 之间的行为存在差异(这似乎是不正常的扩展精度数字)?

Eri*_*hil 8

由 128 位整数初始化形成的对象无效,因为其显式有效位与其他位不匹配。

Apple Clang 使用的是 Intel 的 80 位浮点格式。根据英特尔 64 位和 IA-32 架构软件开发人员手册(2017 年 12 月)4.2.2,“整数”位明确设置为 1 表示无穷大、正常数和 NaN,设置为 0 表示次正规数和零。整数1位是有效数的前导位,编码中的位 63。

有效数的 64 位位于 128 位整数的低位,问题中的代码将它们设置为 63696c400A2D2D21 16。其中,第 63 位为 0(高位 6 为 0110 2)。由于指数字段是 6E65 16(在第 79 到 64 位),这应该是一个正常数,所以第 63 位应该是 1。

当 Integer 位设置不正确时,我没有看到行为规范,因此我们可能认为它没有定义。(人们可能想知道这是否是 的故意行为isnan,因为它“正确”地报告了无效编码不是数字。)

0x6369…更正为 时0xE369…,程序会正确报告该值不是 NaN。

脚注

1之所以如此称呼是因为有效数通常表示为bbbb ... bbb,其中前导位是小数点的唯一左侧,因此是唯一表示整数值的位。有效数的其余位是小数位。