You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

strtoull()转double再转回uint64_t时的精度丢失差异问题

为什么uint64_t转double再转回的结果会因初始值来源不同而不同?

先看你给出的第一段测试代码:

#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

再看补充的测试代码:

#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

这个差异的核心原因可以拆成两点来解释:

1. double的精度限制无法精确存储0xFFFFFFFFFFFFFFFF

0xFFFFFFFFFFFFFFFF是uint64_t的最大值,也就是2^64 - 1,它的二进制是64位全1。而double是64位浮点数,只有53位的有效精度(52位尾数+1位隐含的最高位1),这意味着任何超过2^53的整数都无法被double精确表示——在这个范围之外,double只能精确表示间隔为2^(n-53)的整数(n是数值的指数位对应的量级)。

对于2^64这个量级,double的最小精度步长是2^(64-52) = 4096,也就是说,double无法表示2^64 - 1这个数,它会被舍入到最近的可表示值:2^64(因为2^64 - 1和2^64的差是1,远小于步长4096,所以会被舍入到更大的那个值)。

而2^64超出了uint64_t的取值范围(uint64_t最大只能到2^64 - 1),当把2^64强制转回uint64_t时,会触发无符号整数的溢出行为,结果就是0——这就是第二个输出为0x0的原因。

2. 编译器的常量折叠优化让第三个结果“看起来正确”

第三个表达式uint64_t(double(uint64_t(0xFFFFFFFFFFFFFFFF)))是一个编译期常量表达式:所有的值都是编译时就能确定的常量,编译器会直接在编译阶段计算整个表达式的结果,而不会等到运行时再执行转换。

编译器在处理这个常量转换时,会发现把0xFFFFFFFFFFFFFFFF转成double再转回uint64_t的行为没有实际意义,甚至会导致溢出,所以它会直接优化掉中间的double转换步骤,直接保留原始的常量值0xFFFFFFFFFFFFFFFF——这就是第三个输出和第一个一致的原因。

而z1是通过std::strtoull函数得到的值,虽然字符串是常量,但函数调用的结果在编译期无法被编译器确定(编译器不会去解析字符串内容并优化成常量),所以z1会被当成运行时变量处理:实际执行转double再转回uint64_t的操作,自然就得到了溢出后的0。

如果想要验证这一点,你可以把z2改成一个运行时才能确定的值(比如用一个函数返回0xFFFFFFFFFFFFFFFF),这时转double再转回的结果也会变成0,和z1的情况一致。

内容的提问来源于stack exchange,提问作者Adama

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 08:48:44