strtoull() 的输出在转换为双精度然后返回 uint64_t 时失去精度

Output of strtoull() loses precision when converted to double and then back to uint64_t

考虑以下因素:

#include <iostream>
#include <cstdint>

int main() {
   std::cout << std::hex
      << "0x" << std::strtoull("0xFFFFFFFFFFFFFFFF",0,16) << std::endl
      << "0x" << uint64_t(double(std::strtoull("0xFFFFFFFFFFFFFFFF",0,16))) << std::endl
      << "0x" << uint64_t(double(uint64_t(0xFFFFFFFFFFFFFFFF))) << std::endl;
   return 0;
}

打印:

0xffffffffffffffff
0x0
0xffffffffffffffff

第一个数字只是将 ULLONG_MAX 从字符串转换为 uint64_t 的结果,按预期工作。

但是,如果我将结果转换为 double,然后返回到 uint64_t,那么它会打印第二个数字 0

通常,我会将此归因于浮点数的精度不准确,但更让我困惑的是,如果我将 ULLONG_MAXuint64_t 转换为 double 然后返回到uint64_t,结果是正确的(第三个数字)。

为什么第二个和第三个结果不一样?

编辑(@Radoslaw Cybulski) 对于这里发生的另一个问题,请尝试以下代码:

#include <iostream>
#include <cstdint>
using namespace std;

int main() {
    uint64_t z1 = std::strtoull("0xFFFFFFFFFFFFFFFF",0,16);
    uint64_t z2 = 0xFFFFFFFFFFFFFFFFull;
    std::cout << z1 << " " << uint64_t(double(z1)) << "\n";
    std::cout << z2 << " " << uint64_t(double(z2)) << "\n";
    return 0;
}

它愉快地打印:

18446744073709551615 0
18446744073709551615 18446744073709551615

最接近 0xFFFFFFFFFFFFFFFF 并且可以用双精度表示(假设 64 位 IEEE)的数字是 18446744073709551616。你会发现这个数字比 0xFFFFFFFFFFFFFFFF 大。因此,该数字超出了 uint64_t.

的可表示范围

关于转换回整数,标准说(引用最新草案):

[conv.fpint]

A prvalue of a floating-point type can be converted to a prvalue of an integer type. The conversion truncates; that is, the fractional part is discarded. The behavior is undefined if the truncated value cannot be represented in the destination type.


Why the discrepancy between the second and the third result?

因为程序的行为是未定义的。

虽然分析UB差异的原因大多毫无意义,因为变化的范围是无限的,但我对这种情况下差异原因的猜测是,在一种情况下该值是编译时间常数,而在另一个是在运行时调用库函数。