当转换为double然后返回uint64_t时,strtoull()的输出会失去精度。

Ada*_*ama 8 c++ precision strtol strtoull

考虑以下:

#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;
}
Run Code Online (Sandbox Code Playgroud)

哪些打印:

0xffffffffffffffff
0x0
0xffffffffffffffff
Run Code Online (Sandbox Code Playgroud)

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

但是,如果我将结果转换为double,然后又转换为uint64_t,则它将输出0第二个数字。

通常,我将其归因于浮点数的精度不准确,但令我感到困惑的是,如果我将ULLONG_MAXfrom转换uint64_tdouble,然后再转换回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;
}
Run Code Online (Sandbox Code Playgroud)

愉快地打印:

18446744073709551615 0
18446744073709551615 18446744073709551615
Run Code Online (Sandbox Code Playgroud)

eer*_*ika 10

最接近0xFFFFFFFFFFFFFFFFFF的数字是18446744073709551616,可以用双精度表示(假设是64位IEEE)。该数字比0xFFFFFFFFFFFFFFFFFF大。因此,该数字超出的可表示范围uint64_t

在转换回整数的过程中,标准说(引用最新草案):

[conf.fpint]

浮点类型的prvalue可以转换为整数类型的prvalue。转换被截断;即,小数部分被丢弃。 如果无法在目标类型中表示截断的值,则该行为未定义


为什么第二和第三结果之间存在差异?

因为程序的行为是不确定的。

尽管由于变化范围是无限的,所以分析UB差异的原因在大多数情况下是没有意义的,但我猜测这种情况下出现差异的原因是,在一种情况下,该值是编译时间常数,而在另一种情况下,该值是编译时间常数。调用在运行时调用的库函数。

  • 我们不打算为此发布另一个答案,但是您可能要提到,对于标准IEEE 754双精度二进制浮点数,它只有53位整数级精度。“ UINT64_C(1)&lt;&lt; 53”是最后一个连续的整数值,可以转换为“ double”并无损地返回;超出该限制的任何奇数值都将四舍五入(最终,随着您越来越依赖指数放大整数分量,数值将不能被4、8、16等整除)。 (4认同)
  • @StackDanny可能是因为较大的double比较小的double更近。结果将取决于当前的舍入模式。 (3认同)