无符号64位最大整数转double:打印误差是否意味着精度损失?
我编写了一段C++代码,用于测试将uint64_t类型的最大值转换为double类型时是否会出现精度损失:
#include <cstdint> #include <limits> #include <iostream> #include <iomanip> int main () noexcept { uint64_t ui64{std::numeric_limits<uint64_t>::max()}; constexpr auto max_precision{std::numeric_limits<long double>::digits10 + 1}; std::cout << "ui64 " << std::setprecision(max_precision) << std::boolalpha << ui64 << "\n\n"; double f64 = static_cast<double>(ui64); uint64_t ui64_cast_back = static_cast<uint64_t>(f64); std::cout << "sizeof(f64): " << sizeof(double) << std::endl; std::cout << "f64 = " << f64 << std::endl; std::cout << "ui64_cast_back matches original value? " << (ui64_cast_back == ui64) << std::endl; std::cout << "ui64_cast_back = " << ui64_cast_back << std::endl; }
编译运行后得到如下输出:
ui64 18446744073709551615 sizeof(f64): 8 f64 = 18446744073709551616 ui64_cast_back matches original value? true ui64_cast_back = 18446744073709551615
打印double值时出现的差1误差似乎暗示存在精度损失,但将其转换回uint64_t后却能得到原始值。请问这种差1情况是由IO流的打印实现导致的,还是可以视为精度损失的证明?
核心结论
这种差1现象既是精度损失的证明,也是IO打印逻辑共同作用的结果,但转换回uint64_t得到原始值是未定义行为导致的巧合,不能说明double精确存储了原始值。
具体解释
精度损失确实存在
uint64_t的最大值是2^64 - 1(即18446744073709551615),其二进制是64位全1。而标准double类型基于IEEE 754,只有53位有效尾数(含隐含的1位),无法精确表示所有超过2^53的整数——对于2^63到2^64区间的整数,double的相邻可表示值步长为2^(64-53) = 2048。2^64 -1离2^64仅差1,远小于步长,因此转换为double时会被舍入到最近的可表示值2^64(即18446744073709551616),这就是精度损失的本质。打印的差1是精度损失的直接表现
cout打印double时,会输出能唯一映射回该double值的最短十进制表示。由于2^64是double可精确表示的值,cout直接输出其十进制形式,这就呈现出与原始uint64_t值差1的结果——这不是打印逻辑的错误,而是如实反映了double存储的实际值。转换回uint64_t得到原始值是未定义行为
2^64已经超出了uint64_t的最大值范围(2^64 -1),根据C++标准,将超出无符号整数范围的浮点值转换为无符号整数属于未定义行为。你的平台上能得到原始值,只是该平台实现的特殊处理(例如将超出值回绕为类型最大值),这种结果不可移植,也不能证明double没有损失精度。
内容的提问来源于stack exchange,提问作者Marko Popovic

