请考虑以下内容:
#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_MAX
从uint64_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的答案)。 - ShadowRanger-O1
及更高级别时,z2
实际上从未存在过;在打印之前,立即将0xffffffffffffffff
的立即值直接加载到参数寄存器中,而不会存储在专用寄存器或堆栈位置中。只有在-O0
(避免干扰调试的优化;它尝试保持代码行和相关汇编之间的对应关系)时,才会为z2
创建堆栈位置,在每次使用时从中加载,对该值执行强制转换等操作。 - ShadowRanger