【问题标题】:Output of strtoull() loses precision when converted to double and then back to uint64_tstrtoull() 的输出在转换为双精度然后返回到 uint64_t 时会丢失精度
【发布时间】:2019-07-19 13:09:24
【问题描述】:

考虑以下几点:

#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

【问题讨论】:

  • 这是未定义行为的线索:在本地测试中,使用 g++ 6.3 版,行为会根据我是否通过优化标志而有所不同。当我通过-O1-O2-O3 时,我符合你的行为。当我没有传递优化标志(或显式传递-O0)时,两次往返转换的结果都是0(检查程序集,只有-O0 实际上执行z2 的转换;优化会跳过它们根据eerorika's answer,标准的基础是任何情况下强制转换会产生影响的情况都是未定义的行为。
  • 澄清检查程序集的结果(Radoslaw 的代码):-O1 及更高版本,z2 实际上永远不存在; 0xffffffffffffffff 的立即值在打印之前直接加载到参数寄存器中,而不会存储在专用寄存器或堆栈位置中。只有在-O0(它避免了会干扰调试的优化;它试图保留代码行和相关程序集之间的对应关系)它才费心为z2创建一个堆栈位置,每次使用时都从它加载, 对值执行强制转换等。

标签: c++ precision strtol strtoull


【解决方案1】:

最接近 0xFFFFFFFFFFFFFFFF 并且可以用 double 表示的数字(假设 64 位 IEEE)是 18446744073709551616。您会发现这是一个比 0xFFFFFFFFFFFFFFFF 更大的数字。因此,该数字超出了uint64_t 的可表示范围。

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

[conv.fpint]

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


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

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

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

【讨论】:

  • 我想知道,为什么转换的目标是较大的最接近的整数而不是较小的?
  • @StackDanny 可能是因为较大的双精度比较小的更近。结果将取决于当前的舍入模式。
  • 不打算为此发布另一个答案,但您可能想提一下,对于标准 IEEE 754 双精度二进制浮点,它只有 53 位整数级精度。 UINT64_C(1) &lt;&lt; 53 是最后一个转换为 double 并无损返回的连续整数值;任何超过该限制的奇数值都会四舍五入(最终,不能被 4、8、16 等整除的值也会四舍五入,因为您越来越依赖指数来放大整数分量)。
猜你喜欢
  • 1970-01-01
  • 2021-09-12
  • 1970-01-01
  • 2013-06-17
  • 1970-01-01
  • 2011-08-24
  • 2012-05-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多