【问题标题】:Why do printf and isnan disagree whether a long double value is a NaN?为什么 printf 和 isnan 不同意 long double 值是否是 NaN?
【发布时间】:2021-02-20 19:13:41
【问题描述】:

考虑以下代码:

#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);
}

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

1
3.345927e+3575

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

iostreams 和 clang++ 也是如此:

std::cout << d; // prints 3.34593e+3575

具体来说,为什么不同的 C 和 C++ API 在处理这个数字(似乎是unnormal extended precision number)时会有不同的行为?

【问题讨论】:

  • long double 的底层类型是什么? 80 位 x87?
  • sizeof(long double) 是 16 但我认为它应该是 80 位扩展精度 FP。
  • @EricPostpischil isnan 是否是宏似乎取决于所使用的语言(至少根据 cppreference)。这个问题用 C 和 C++ 标记。对于 C,isnan is a macro,但对于 C++ isnan is an overloaded function。鉴于代码中缺少 C++ 特性,或许应该去掉 C++ 标签?
  • 无论该行为是否发生在 C 和 C++ 中,您都不应向语言标签发送垃圾邮件,因为它会不必要地吸引其他人的注意力,并且该行为不依赖于语言。组合语言标签应保留用于询问有关语言之间差异或交互的问题。对于这个问题,C 和 C++ 标记都可以省略。
  • 我不认为这就是语言标签在实践中的使用方式,即使粗略查看已标记的问题也能看出这一点。

标签: c++ c floating-point clang


【解决方案1】:

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

Apple Clang 使用的是 Intel 的 80 位浮点格式。根据 Intel 64 and IA-32 Architectures Software Developer's Manual(2017 年 12 月)4.2.2,“整数”位明确设置为 1 表示无穷大、正常数和 NaN,0 表示次正规数和零。 Integer1 位是有效数字的前导位,编码中的位 63。

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

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

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

脚注

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-05-04
    • 2013-07-10
    • 1970-01-01
    • 2013-02-05
    • 1970-01-01
    • 2020-10-30
    • 1970-01-01
    相关资源
    最近更新 更多