如何解决Win32上将double类型转换为unsigned int会截断为2,147,483,648
编译以下代码:
double getDouble()
{
double value = 2147483649.0;
return value;
}
int main()
{
printf("INT_MAX: %u\n",INT_MAX);
printf("UINT_MAX: %u\n",UINT_MAX);
printf("Double value: %f\n",getDouble());
printf("Direct cast value: %u\n",(unsigned int) getDouble());
double d = getDouble();
printf("Indirect cast value: %u\n",(unsigned int) d);
return 0;
}
输出(MSVC x86):
INT_MAX: 2147483647
UINT_MAX: 4294967295
Double value: 2147483649.000000
Direct cast value: 2147483648
Indirect cast value: 2147483649
输出(MSVC x64):
INT_MAX: 2147483647
UINT_MAX: 4294967295
Double value: 2147483649.000000
Direct cast value: 2147483649
Indirect cast value: 2147483649
在Microsoft documentation中,从double
到unsigned int
的转换中没有提到带符号整数最大值。
函数返回时,INT_MAX
以上的所有值都将被截断为2147483648
。
我正在使用 Visual Studio 2019 来构建程序。在 gcc 上不会发生这种情况。
我做错什么了吗?是否有将double
转换为unsigned int
的安全方法?
解决方法
编译器错误...
在@anastaciu提供的程序集中,直接转换代码调用__ftol2_sse
,这似乎将数字转换为带符号的长。例程名称为ftol2_sse
,因为它是启用了sse的计算机-但浮点数位于x87浮点寄存器中。
; Line 17
call _getDouble
call __ftol2_sse
push eax
push OFFSET ??_C@_0BH@GDLBDFEH@Direct?5cast?5value?3?5?$CFu?6@
call _printf
add esp,8
另一方面,间接演员确实如此
; Line 18
call _getDouble
fstp QWORD PTR _d$[ebp]
; Line 19
movsd xmm0,QWORD PTR _d$[ebp]
call __dtoui3
push eax
push OFFSET ??_C@_0BJ@HCKMOBHF@Indirect?5cast?5value?3?5?$CFu?6@
call _printf
add esp,8
弹出并把双精度值存储到本地变量,然后将其加载到SSE寄存器中并调用__dtoui3
,这是双精度到无符号的int转换例程...
直接投射的行为不符合C89;也不符合任何更高版本-偶 C89明确表示:
当将整数类型的值转换为无符号类型时,无需执行余数运算。因此,可移植值的范围为 [0,Utype_MAX + 1)。
我认为问题可能是continuation of this from 2005-曾经有一个名为__ftol2
的转换函数可能适用于此代码,即它将值转换为签名的数字 -2147483647,当解释为未签名的数字时,该数字会产生正确的结果。
不幸的是,__ftol2_sse
并不是__ftol2
的直接替代品,因为它会-而不是仅将最低有效值保持不变,而是通过以下方式发出超出范围的错误信号:返回LONG_MIN
/ 0x80000000
,此处解释为unsigned long根本不是预期的结果。 __ftol2_sse
的行为对于signed long
是有效的,因为将> LONG_MAX
的双精度值转换为signed long
会产生不确定的行为。
在@AnttiHaapala's answer之后,我使用优化/Ox
对代码进行了测试,发现由于__ftol2_sse
不再使用该错误将被删除:
//; 17 : printf("Direct cast value: %u\n",(unsigned int)getDouble());
push -2147483647 //; 80000001H
push OFFSET $SG10116
call _printf
//; 18 : double d = getDouble();
//; 19 : printf("Indirect cast value: %u\n",(unsigned int)d);
push -2147483647 //; 80000001H
push OFFSET $SG10117
call _printf
add esp,28 //; 0000001cH
优化内联了getdouble()
并添加了常量表达式求值,从而消除了在运行时进行转换的需要,从而消除了错误。
出于好奇,我进行了更多测试,即更改代码以在运行时强制进行浮点到整数转换。在这种情况下,结果仍然是正确的,经过优化的编译器在两次转换中均使用__dtoui3
:
//; 19 : printf("Direct cast value: %u\n",(unsigned int)getDouble(d));
movsd xmm0,QWORD PTR _d$[esp+24]
add esp,12 //; 0000000cH
call __dtoui3
push eax
push OFFSET $SG9261
call _printf
//; 20 : double db = getDouble(d);
//; 21 : printf("Indirect cast value: %u\n",(unsigned int)db);
movsd xmm0,QWORD PTR _d$[esp+20]
add esp,8
call __dtoui3
push eax
push OFFSET $SG9262
call _printf
但是,为了防止内联,__declspec(noinline) double getDouble(){...}
会使错误重新出现:
//; 17 : printf("Direct cast value: %u\n",QWORD PTR _d$[esp+76]
add esp,4
movsd QWORD PTR [esp],xmm0
call _getDouble
call __ftol2_sse
push eax
push OFFSET $SG9261
call _printf
//; 18 : double db = getDouble(d);
movsd xmm0,QWORD PTR _d$[esp+80]
add esp,8
movsd QWORD PTR [esp],xmm0
call _getDouble
//; 19 : printf("Indirect cast value: %u\n",(unsigned int)db);
call __ftol2_sse
push eax
push OFFSET $SG9262
call _printf
在两次转换中都调用 __ftol2_sse
,使两种情况下的输出2147483648
, @zwol suspicions都是正确的。
编译详细信息:
- 使用命令行:
cl /permissive- /GS /analyze- /W3 /Gm- /Ox /sdl /D "WIN32" program.c
-
在Visual Studio中:
-
在 项目
RTC
属性 {{ 1}} 代码生成 ,并将基本运行时检查设置为 默认 。 > -
在 项目
->
属性->
优化 ,并将优化设置为 / Ox 。 -
在
->
模式下使用调试器。
-
没有人查看过MS的__ftol2_sse
的汇编。
从结果中,我们可以推断出它可能是从x87转换为带符号的int
/ long
(在Windows上均为32位类型),而不是安全地转换为uint32_t
。 / p>
x86 FP->溢出整数结果的整数指令不仅会包装/截断:当目标中无法显示确切值时,它们会产生Intel所谓的“整数不确定” : 高位设置,其他位清除。即0x80000000
。
(或者,如果未屏蔽FP无效异常,则将触发并且不存储任何值。但是在默认FP环境中,所有FP异常都被屏蔽。这就是为什么对于FP计算,您可以获取NaN而不是错误的原因)
这包括x87指令(例如fistp
(使用当前的舍入模式)和SSE2指令(例如cvttsd2si eax,xmm0
(使用向0的截断,这就是额外的t
的意思))。
因此将double
-> unsigned
转换编译为对__ftol2_sse
的调用是一个错误。
注释/切线
在x86-64上,FP-> uint32_t可以编译为cvttsd2si rax,xmm0
,转换为64位带符号的目标,并在整数目标的下半部分(EAX)中生成所需的uint32_t。
如果结果超出0..2 ^ 32-1范围,则为C和C ++ UB,因此可以确定,巨大的正值或负值将使整数不确定位模式的RAX(EAX)的下半部分为零。 (与整数->整数转换不同, 不能保证值的模减少。Is the behaviour of casting a negative double to unsigned int defined in the C standard? Different behaviour on ARM vs. x86。要清楚的是,问题中的任何内容都不确定或什至没有实现定义的行为。我只是指出,如果您具有FP-> int64_t,则可以使用它来有效地实现FP-> uint32_t。其中包括x87 fistp
,即使在32位和16位模式不同于SSE2指令,后者只能在64位模式下直接处理64位整数。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。