Visual Studio 调试与发布版本:比较 int 和 float 不匹配
Visual Studio debug vs. release build: comparing int and float missmatch
看看这个例子:
#include <stdio.h>
int main() {
int i= 16777217;
float f = 16777216.0;
float g = i;
if( i == f )
printf("eq\n");
else
printf("neq\n");
if( g == f )
printf("eq\n");
else
printf("neq\n");
return 0;
}
在发布模式下使用 Visual Studio 2010 C++ (VS)、gcc 或 g++ (4.9.2),输出为
eq
eq
这对我来说是合理的:在第一次比较期间,i
被隐式转换为浮点数,其中尾数中的有效位被截断。因此,i
和 f
都具有相同的位模式,与相等性相比。在第二个 if
中应用相同的转换,但在定义和初始化 g
.
时已经执行
但是,在调试模式下使用VS,结果是
neq
eq
看起来,第一个 if
中比较期间的隐式转换(作为 C 和 C++ 中常用算术转换的一部分)并未应用。这是真的?是否有一种 VS 机制可以防止在比较浮点数和整数时出现此类误报(更精确地转换为 int/float)?根据 MSDN VS C++ 遵循标准。
我用this function检查了位表示。对于所有编译器,它产生
i = 00000001000000000000000000000001
f = 01001011100000000000000000000000
g = 01001011100000000000000000000000
float.h
在 VS 状态下 #define FLT_MANT_DIG 24
因此所描述的截断问题也应该成立。
我在同一台机器 (Intel i5-3570K) 上编译了所有内容,但在虚拟框中编译了 VS。在另一台机器上用 VS 编译也打印 neq/eq
。
编辑:附加汇编代码
differences_debug.asm
; Listing generated by Microsoft (R) Optimizing Compiler Version 16.00.30319.01
TITLE c:\Users\user\documents\visual studio 2010\Projects\differences\differences\differences.cpp
.686P
.XMM
include listing.inc
.model flat
INCLUDELIB MSVCRTD
INCLUDELIB OLDNAMES
PUBLIC ??_C@_04LMPLCMBC@neq?6?$AA@ ; `string'
PUBLIC ??_C@_03HNJPMNDP@eq?6?$AA@ ; `string'
PUBLIC __real@4b800000
PUBLIC _wmain
EXTRN __imp__printf:PROC
EXTRN __fltused:DWORD
EXTRN __RTC_CheckEsp:PROC
EXTRN __RTC_Shutdown:PROC
EXTRN __RTC_InitBase:PROC
; COMDAT ??_C@_04LMPLCMBC@neq?6?$AA@
; File c:\users\user\documents\visual studio 2010\projects\differences\differences\differences.cpp
CONST SEGMENT
??_C@_04LMPLCMBC@neq?6?$AA@ DB 'neq', 0aH, 00H ; `string'
CONST ENDS
; COMDAT ??_C@_03HNJPMNDP@eq?6?$AA@
CONST SEGMENT
??_C@_03HNJPMNDP@eq?6?$AA@ DB 'eq', 0aH, 00H ; `string'
CONST ENDS
; COMDAT __real@4b800000
CONST SEGMENT
__real@4b800000 DD 04b800000r ; 1.67772e+007
CONST ENDS
; COMDAT rtc$TMZ
rtc$TMZ SEGMENT
__RTC_Shutdown.rtc$TMZ DD FLAT:__RTC_Shutdown
rtc$TMZ ENDS
; COMDAT rtc$IMZ
rtc$IMZ SEGMENT
__RTC_InitBase.rtc$IMZ DD FLAT:__RTC_InitBase
; Function compile flags: /Odtp /RTCsu /ZI
rtc$IMZ ENDS
; COMDAT _wmain
_TEXT SEGMENT
_g$ = -32 ; size = 4
_f$ = -20 ; size = 4
_i$ = -8 ; size = 4
_argc$ = 8 ; size = 4
_argv$ = 12 ; size = 4
_wmain PROC ; COMDAT
; Line 7
push ebp
mov ebp, esp
sub esp, 228 ; 000000e4H
push ebx
push esi
push edi
lea edi, DWORD PTR [ebp-228]
mov ecx, 57 ; 00000039H
mov eax, -858993460 ; ccccccccH
rep stosd
; Line 8
mov DWORD PTR _i$[ebp], 16777217 ; 01000001H
; Line 9
fld DWORD PTR __real@4b800000
fstp DWORD PTR _f$[ebp]
; Line 10
fild DWORD PTR _i$[ebp]
fstp DWORD PTR _g$[ebp]
; Line 13
fild DWORD PTR _i$[ebp]
fld DWORD PTR _f$[ebp]
fucompp
fnstsw ax
test ah, 68 ; 00000044H
jp SHORT $LN4@wmain
; Line 14
mov esi, esp
push OFFSET ??_C@_03HNJPMNDP@eq?6?$AA@
call DWORD PTR __imp__printf
add esp, 4
cmp esi, esp
call __RTC_CheckEsp
; Line 15
jmp SHORT $LN3@wmain
$LN4@wmain:
; Line 16
mov esi, esp
push OFFSET ??_C@_04LMPLCMBC@neq?6?$AA@
call DWORD PTR __imp__printf
add esp, 4
cmp esi, esp
call __RTC_CheckEsp
$LN3@wmain:
; Line 19
fld DWORD PTR _g$[ebp]
fld DWORD PTR _f$[ebp]
fucompp
fnstsw ax
test ah, 68 ; 00000044H
jp SHORT $LN2@wmain
; Line 20
mov esi, esp
push OFFSET ??_C@_03HNJPMNDP@eq?6?$AA@
call DWORD PTR __imp__printf
add esp, 4
cmp esi, esp
call __RTC_CheckEsp
; Line 21
jmp SHORT $LN1@wmain
$LN2@wmain:
; Line 22
mov esi, esp
push OFFSET ??_C@_04LMPLCMBC@neq?6?$AA@
call DWORD PTR __imp__printf
add esp, 4
cmp esi, esp
call __RTC_CheckEsp
$LN1@wmain:
; Line 24
xor eax, eax
; Line 26
pop edi
pop esi
pop ebx
add esp, 228 ; 000000e4H
cmp ebp, esp
call __RTC_CheckEsp
mov esp, ebp
pop ebp
ret 0
_wmain ENDP
_TEXT ENDS
END
differences_release.asm
; Listing generated by Microsoft (R) Optimizing Compiler Version 16.00.30319.01
TITLE c:\Users\user\documents\visual studio 2010\Projects\differences\differences\differences.cpp
.686P
.XMM
include listing.inc
.model flat
INCLUDELIB OLDNAMES
PUBLIC ??_C@_03HNJPMNDP@eq?6?$AA@ ; `string'
PUBLIC ??_C@_04LMPLCMBC@neq?6?$AA@ ; `string'
EXTRN @__security_check_cookie@4:PROC
EXTRN __imp__printf:PROC
; COMDAT ??_C@_04LMPLCMBC@neq?6?$AA@
CONST SEGMENT
??_C@_04LMPLCMBC@neq?6?$AA@ DB 'neq', 0aH, 00H ; `string'
CONST ENDS
; COMDAT ??_C@_03HNJPMNDP@eq?6?$AA@
CONST SEGMENT
??_C@_03HNJPMNDP@eq?6?$AA@ DB 'eq', 0aH, 00H ; `string'
CONST ENDS
PUBLIC _wmain
EXTRN __fltused:DWORD
; Function compile flags: /Ogtp
; COMDAT _wmain
_TEXT SEGMENT
_argc$ = 8 ; size = 4
_argv$ = 12 ; size = 4
_wmain PROC ; COMDAT
; File c:\users\user\documents\visual studio 2010\projects\differences\differences\differences.cpp
; Line 7
push esi
; Line 14
mov esi, DWORD PTR __imp__printf
push OFFSET ??_C@_03HNJPMNDP@eq?6?$AA@
call esi
; Line 20
push OFFSET ??_C@_03HNJPMNDP@eq?6?$AA@
call esi
add esp, 8
; Line 24
xor eax, eax
pop esi
; Line 26
ret 0
_wmain ENDP
_TEXT ENDS
END
如果我们分解发行版 ASM:
; Line 14
push OFFSET ??_C@_03HNJPMNDP@eq?6?$AA@
call DWORD PTR __imp__printf
add esp, 4
; Line 18
xor eax, eax
; Line 20
ret 0
它只是打印 eq
并退出,这表明浮动比较刚刚被完全优化掉。对于调试程序集,我们使用 fld
和 fild
指令查看它:
; Line 9
fld DWORD PTR __real@4b800000
fstp DWORD PTR _f$[ebp]
; Line 10
fild DWORD PTR _i$[ebp]
fstp DWORD PTR _g$[ebp]
; Line 13
fild DWORD PTR _i$[ebp]
这些 IA32
指令是 Visual Studio 2010 中使用的默认架构。我怀疑您使用 /arch:SSE2
而不是您会得到不同的结果。
基本上证实了我刚才所说的。
两组输出均符合 C 行为。
在执行 FP 数学时,C 允许 FP 计算以比操作数格式更高的精度级别进行。
如果代码执行 i == f
作为 double
数学,结果是 "neq"
。
如果代码执行 i == f
作为 float
数学运算,则结果为 "eq"
。
int i= 16777217;
float f = 16777216.0;
if( i == f )
printf("eq\n");
else
printf("neq\n");
Except for assignment and cast (which remove all extra range and precision), the values yielded by operators with floating operands and values subject to the usual arithmetic conversions and of floating constants are evaluated to a format whose range and precision may be greater than required by the type C11 §5.2.4.2.2 9
现代 C 编译器提供 FLT_EVAL_METHOD
指示使用的内容。
Is there a VS-mechanism which prevents such false positives in comparing floats and ints (conversion to int/float with more precision)?
要强制进行 float
比较,代码可以使用
if((float) i == f )
要强制进行 double
比较,代码可以使用
if((double) i == f )
看看这个例子:
#include <stdio.h>
int main() {
int i= 16777217;
float f = 16777216.0;
float g = i;
if( i == f )
printf("eq\n");
else
printf("neq\n");
if( g == f )
printf("eq\n");
else
printf("neq\n");
return 0;
}
在发布模式下使用 Visual Studio 2010 C++ (VS)、gcc 或 g++ (4.9.2),输出为
eq
eq
这对我来说是合理的:在第一次比较期间,i
被隐式转换为浮点数,其中尾数中的有效位被截断。因此,i
和 f
都具有相同的位模式,与相等性相比。在第二个 if
中应用相同的转换,但在定义和初始化 g
.
但是,在调试模式下使用VS,结果是
neq
eq
看起来,第一个 if
中比较期间的隐式转换(作为 C 和 C++ 中常用算术转换的一部分)并未应用。这是真的?是否有一种 VS 机制可以防止在比较浮点数和整数时出现此类误报(更精确地转换为 int/float)?根据 MSDN VS C++ 遵循标准。
我用this function检查了位表示。对于所有编译器,它产生
i = 00000001000000000000000000000001
f = 01001011100000000000000000000000
g = 01001011100000000000000000000000
float.h
在 VS 状态下 #define FLT_MANT_DIG 24
因此所描述的截断问题也应该成立。
我在同一台机器 (Intel i5-3570K) 上编译了所有内容,但在虚拟框中编译了 VS。在另一台机器上用 VS 编译也打印 neq/eq
。
编辑:附加汇编代码
differences_debug.asm
; Listing generated by Microsoft (R) Optimizing Compiler Version 16.00.30319.01
TITLE c:\Users\user\documents\visual studio 2010\Projects\differences\differences\differences.cpp
.686P
.XMM
include listing.inc
.model flat
INCLUDELIB MSVCRTD
INCLUDELIB OLDNAMES
PUBLIC ??_C@_04LMPLCMBC@neq?6?$AA@ ; `string'
PUBLIC ??_C@_03HNJPMNDP@eq?6?$AA@ ; `string'
PUBLIC __real@4b800000
PUBLIC _wmain
EXTRN __imp__printf:PROC
EXTRN __fltused:DWORD
EXTRN __RTC_CheckEsp:PROC
EXTRN __RTC_Shutdown:PROC
EXTRN __RTC_InitBase:PROC
; COMDAT ??_C@_04LMPLCMBC@neq?6?$AA@
; File c:\users\user\documents\visual studio 2010\projects\differences\differences\differences.cpp
CONST SEGMENT
??_C@_04LMPLCMBC@neq?6?$AA@ DB 'neq', 0aH, 00H ; `string'
CONST ENDS
; COMDAT ??_C@_03HNJPMNDP@eq?6?$AA@
CONST SEGMENT
??_C@_03HNJPMNDP@eq?6?$AA@ DB 'eq', 0aH, 00H ; `string'
CONST ENDS
; COMDAT __real@4b800000
CONST SEGMENT
__real@4b800000 DD 04b800000r ; 1.67772e+007
CONST ENDS
; COMDAT rtc$TMZ
rtc$TMZ SEGMENT
__RTC_Shutdown.rtc$TMZ DD FLAT:__RTC_Shutdown
rtc$TMZ ENDS
; COMDAT rtc$IMZ
rtc$IMZ SEGMENT
__RTC_InitBase.rtc$IMZ DD FLAT:__RTC_InitBase
; Function compile flags: /Odtp /RTCsu /ZI
rtc$IMZ ENDS
; COMDAT _wmain
_TEXT SEGMENT
_g$ = -32 ; size = 4
_f$ = -20 ; size = 4
_i$ = -8 ; size = 4
_argc$ = 8 ; size = 4
_argv$ = 12 ; size = 4
_wmain PROC ; COMDAT
; Line 7
push ebp
mov ebp, esp
sub esp, 228 ; 000000e4H
push ebx
push esi
push edi
lea edi, DWORD PTR [ebp-228]
mov ecx, 57 ; 00000039H
mov eax, -858993460 ; ccccccccH
rep stosd
; Line 8
mov DWORD PTR _i$[ebp], 16777217 ; 01000001H
; Line 9
fld DWORD PTR __real@4b800000
fstp DWORD PTR _f$[ebp]
; Line 10
fild DWORD PTR _i$[ebp]
fstp DWORD PTR _g$[ebp]
; Line 13
fild DWORD PTR _i$[ebp]
fld DWORD PTR _f$[ebp]
fucompp
fnstsw ax
test ah, 68 ; 00000044H
jp SHORT $LN4@wmain
; Line 14
mov esi, esp
push OFFSET ??_C@_03HNJPMNDP@eq?6?$AA@
call DWORD PTR __imp__printf
add esp, 4
cmp esi, esp
call __RTC_CheckEsp
; Line 15
jmp SHORT $LN3@wmain
$LN4@wmain:
; Line 16
mov esi, esp
push OFFSET ??_C@_04LMPLCMBC@neq?6?$AA@
call DWORD PTR __imp__printf
add esp, 4
cmp esi, esp
call __RTC_CheckEsp
$LN3@wmain:
; Line 19
fld DWORD PTR _g$[ebp]
fld DWORD PTR _f$[ebp]
fucompp
fnstsw ax
test ah, 68 ; 00000044H
jp SHORT $LN2@wmain
; Line 20
mov esi, esp
push OFFSET ??_C@_03HNJPMNDP@eq?6?$AA@
call DWORD PTR __imp__printf
add esp, 4
cmp esi, esp
call __RTC_CheckEsp
; Line 21
jmp SHORT $LN1@wmain
$LN2@wmain:
; Line 22
mov esi, esp
push OFFSET ??_C@_04LMPLCMBC@neq?6?$AA@
call DWORD PTR __imp__printf
add esp, 4
cmp esi, esp
call __RTC_CheckEsp
$LN1@wmain:
; Line 24
xor eax, eax
; Line 26
pop edi
pop esi
pop ebx
add esp, 228 ; 000000e4H
cmp ebp, esp
call __RTC_CheckEsp
mov esp, ebp
pop ebp
ret 0
_wmain ENDP
_TEXT ENDS
END
differences_release.asm
; Listing generated by Microsoft (R) Optimizing Compiler Version 16.00.30319.01
TITLE c:\Users\user\documents\visual studio 2010\Projects\differences\differences\differences.cpp
.686P
.XMM
include listing.inc
.model flat
INCLUDELIB OLDNAMES
PUBLIC ??_C@_03HNJPMNDP@eq?6?$AA@ ; `string'
PUBLIC ??_C@_04LMPLCMBC@neq?6?$AA@ ; `string'
EXTRN @__security_check_cookie@4:PROC
EXTRN __imp__printf:PROC
; COMDAT ??_C@_04LMPLCMBC@neq?6?$AA@
CONST SEGMENT
??_C@_04LMPLCMBC@neq?6?$AA@ DB 'neq', 0aH, 00H ; `string'
CONST ENDS
; COMDAT ??_C@_03HNJPMNDP@eq?6?$AA@
CONST SEGMENT
??_C@_03HNJPMNDP@eq?6?$AA@ DB 'eq', 0aH, 00H ; `string'
CONST ENDS
PUBLIC _wmain
EXTRN __fltused:DWORD
; Function compile flags: /Ogtp
; COMDAT _wmain
_TEXT SEGMENT
_argc$ = 8 ; size = 4
_argv$ = 12 ; size = 4
_wmain PROC ; COMDAT
; File c:\users\user\documents\visual studio 2010\projects\differences\differences\differences.cpp
; Line 7
push esi
; Line 14
mov esi, DWORD PTR __imp__printf
push OFFSET ??_C@_03HNJPMNDP@eq?6?$AA@
call esi
; Line 20
push OFFSET ??_C@_03HNJPMNDP@eq?6?$AA@
call esi
add esp, 8
; Line 24
xor eax, eax
pop esi
; Line 26
ret 0
_wmain ENDP
_TEXT ENDS
END
如果我们分解发行版 ASM:
; Line 14
push OFFSET ??_C@_03HNJPMNDP@eq?6?$AA@
call DWORD PTR __imp__printf
add esp, 4
; Line 18
xor eax, eax
; Line 20
ret 0
它只是打印 eq
并退出,这表明浮动比较刚刚被完全优化掉。对于调试程序集,我们使用 fld
和 fild
指令查看它:
; Line 9
fld DWORD PTR __real@4b800000
fstp DWORD PTR _f$[ebp]
; Line 10
fild DWORD PTR _i$[ebp]
fstp DWORD PTR _g$[ebp]
; Line 13
fild DWORD PTR _i$[ebp]
这些 IA32
指令是 Visual Studio 2010 中使用的默认架构。我怀疑您使用 /arch:SSE2
而不是您会得到不同的结果。
两组输出均符合 C 行为。
在执行 FP 数学时,C 允许 FP 计算以比操作数格式更高的精度级别进行。
如果代码执行 i == f
作为 double
数学,结果是 "neq"
。
如果代码执行 i == f
作为 float
数学运算,则结果为 "eq"
。
int i= 16777217;
float f = 16777216.0;
if( i == f )
printf("eq\n");
else
printf("neq\n");
Except for assignment and cast (which remove all extra range and precision), the values yielded by operators with floating operands and values subject to the usual arithmetic conversions and of floating constants are evaluated to a format whose range and precision may be greater than required by the type C11 §5.2.4.2.2 9
现代 C 编译器提供 FLT_EVAL_METHOD
指示使用的内容。
Is there a VS-mechanism which prevents such false positives in comparing floats and ints (conversion to int/float with more precision)?
要强制进行 float
比较,代码可以使用
if((float) i == f )
要强制进行 double
比较,代码可以使用
if((double) i == f )