Visual Studio 2022中double转long long越界转换的行为疑问
以下是使用Visual Studio 2022编译的C++代码:
#include <iostream> #include <intrin.h> int main() { std::cout << _cvt_dtoll_sent(1e31) << "\n"; std::cout << _cvt_dtoll_sat(1e31) << "\n"; std::cout << _cvt_dtoll_fast(1e31) << "\n"; std::cout << (long long)1e31 << "\n"; }
在x86环境下的输出为:
-9223372036854775808 9223372036854775807 4016683251292700672 -4571153621781053440
在x64环境下的输出为:
-9223372036854775808 9223372036854775807 -9223372036854775808 -4571153621781053440
该程序采用四种不同方式将double类型转换为long long类型。由于1e31无法容纳到long long(64位有符号整数)中,按照C++标准这种行为属于未定义行为。但VS2022默认启用/fpcvt:BC选项,该选项文档说明:
The /fpcvt:BC option also makes conversion to signed types return the minimum possible value when the source is invalid
因此预期(long long)1e31会像_cvt_dtoll_sent一样返回哨兵值-9223372036854775808,但实际结果既不是哨兵值,也与饱和转换或快速转换的结果不一致,请问这是为什么?
原因分析
编译期常量转换与运行时转换的差异
代码中的1e31是编译期可见的double常量,编译器会在编译阶段直接计算(long long)1e31的结果,而非运行时调用浮点转整数的CPU指令。/fpcvt:BC选项的规则主要针对运行时的浮点转换指令行为,对编译器编译期的常量表达式转换逻辑不生效。内置函数与强制转换的实现逻辑不同
_cvt_dtoll_sent、_cvt_dtoll_sat等内置函数是直接封装了CPU的特定转换指令(比如x86的CVTSD2SI指令配合不同的MXCSR控制位),严格遵循/fpcvt:BC对运行时转换的规则。而编译期常量转换是编译器内部的数值计算逻辑,没有使用这些指令,因此结果不受同一规则约束。运行时变量转换的验证
如果将代码修改为运行时赋值的double变量,比如:#include <iostream> #include <intrin.h> int main() { double d = 1e31; std::cout << _cvt_dtoll_sent(d) << "\n"; std::cout << (long long)d << "\n"; }此时
(long long)d的输出会和_cvt_dtoll_sent(d)一致,返回哨兵值-9223372036854775808,符合/fpcvt:BC的预期——因为这时候转换是在运行时通过CPU指令完成的,受选项规则约束。编译期常量转换的结果来源
你看到的-4571153621781053440是编译器对1e31这个double值进行截断转换的结果:1e31的二进制表示作为double,其低64位被直接当作有符号整数解释,就得到了这个值——这是编译器编译期常量转换的默认逻辑,和/fpcvt:BC针对运行时的规则无关。
内容的提问来源于stack exchange,提问作者yeputons

