level 13
Visual C++ 开启 /QIfist 选项可以提高浮点数取整的速度, 精度可能会有影响, 但今天却碰巧发现了一处很严重的问题.
下面两行代码应该都显示 -1.0 才对, 但如果开启 /QIfist, 前者是
正确的
, 而后者会输出 -2.0. 我想这不是精度的问题, 而是bug才对.
printf("%f\n", (float)(int)(-1.5));
printf("%f\n", (float)(int)atof("-1.5"));
已测试 VC6, VC2003, VC2010 均能重现 (注意在编译选项中加入参数"/QIfist")
2009年12月11日 10点12分
1
level 13
/QIfist 确实尽量能不用就不用,并不是误差的问题,而是和浮点状态有关. VC2010已经对此选项提示警告了.
2009年12月11日 11点12分
2
level 9
/QIfist和/arch:SSE或/arch:SSE2冲突,所以对我来说没法用
C/C++默认的取整是truncate,而x86上fpu默认的取整方式是round。所以(int)(-1.5)需要先更改fpu的控制字,转换,然后再把控制字改回去以免影响其他功能。
加入/QIfist后,浮点取整将不再考虑控制字的问题,于是取整变成了round。我记得按照IEEE 754的规定,-1.5在round时应该是50%概率-1,50%概率-2,所以这个结果不能算错。
SSE/SSE2里都有指令直接进行truncate,所以round和truncate的速度没有区别。SSE3给fpu也加入了truncate指令,所以用fpu也没有速度问题了。
其实大部分情况round比truncate有用,天知道当时C语言是怎么设计的,害得现在连硬件都不得不妥协。
比较尴尬的是M$VC的居然没有round函数,而VC糟糕的内联汇编使得无法写出最快的round函数,比较头痛。
2009年12月11日 13点12分
3
level 13
其实把1楼的 1.5 改成 1.6, 1.99, ... 结果都不变,反汇编都看不到内联fistp指令,确实是bug,估计微软只能回答不要用 /QIfist 了.
VC2005以后memcpy和memset的内联被取消很可惜,只能自行写个内联+汇编实现的版本,但没法很好地优化.
SSE/SSE2的取整我不确定能否真正内联.
2009年12月11日 13点12分
4
level 13
truncate(向0取整)是很有用的,很多情况取整不是为了精确取值.
2009年12月11日 13点12分
5
level 9
SSE/SSE2的取整的确是内联,只要一条指令cvttss2si或者cvttsd2si。不过没有double转int64的指令,所以还是需要用fistp实现。
1楼的bug我觉得问题出在常数上,(float)(int)(-1.5)被编译器直接变成常数了。
2009年12月11日 14点12分
6
level 13
纠正一下4楼,fistp指令有内联,确实默认不是trancate,只要之前调用一个函数即可解决:
_controlfp(_MCW_RC, _RC_CHOP);
2009年12月11日 14点12分
7
level 13
我使用VS2005编译并反汇编,没有发现内联取整函数,即使加入/fp:fast,/arch:XXX都是嵌入ftol函数.
2009年12月11日 14点12分
8
level 13
嗯...看起来1楼的问题也不算是bug,只是编译时和运行时的浮点取整模式不同,编译时是截断,而运行时ftol也是截断,但fist默认是最接近,看来要用/QIfist,至少在入口开始处调用controlfp设置一下.
2009年12月11日 14点12分
9
level 9
前面说过了,controlfp以后,有些浮点运算结果会不同的。
要实现内联可能需要开启/Oi和/Ob2,反正我自己的程序用到转换看反汇编都是内联的,具体用fp还是sse要看情况。
贡献两个VC下的内联函数
static inline int lrintf(float x)
{
__asm cvtss2si eax, x
}
static inline __int64 llrint(double x)
{
__asm fld QWORD PTR x
__asm fistp QWORD PTR x
return *(__int64*)&x;
}
2009年12月11日 14点12分
10
level 13
开 /Oi /Ob2 /arch:SSE2 也无效,只要不加/QIfist参数,浮点取整就是调用CRT里的ftol函数,而且是用st0作为参数传入的.
2009年12月11日 15点12分
11
level 9
好奇怪,你的代码是什么样的?我用的是/arch:SSE
有时候内联一个cvttss2si reg32, xmm指令,有时候内联fld和fistp,并且会打乱指令顺序以减少dependency
2009年12月11日 18点12分
12
level 13
VC2005命令行:
/O2 /Ob2 /Oi /Ot /Oy /GL /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /GF /FD /MD /GS- /arch:SSE2 /fp:fast /Fo"Release\\" /Fd"Release\vc80.pdb" /W3 /nologo /c /Wp64 /Zi /TP /errorReport:prompt
printf("%f\n", (float)(int)(-1.9));
printf("%f\n", (float)(int)atof("-1.9"));
00401000 fld qword ptr [402100]
00401006 push esi
00401007 mov esi,[<&MSVCR80.printf>] ; MSVCR80.printf
0040100D sub esp,8 ; /<%f>
00401010 fstp qword ptr [esp] ; |
00401013 push 004020F4 ; |format = "%f",LF,""
00401018 call esi ; \printf
0040101A push 004020F8 ; /s = "-1.9"
0040101F call [<&MSVCR80.atof>] ; \atof
00401025 call 00401820 ; 这就是ftol函数
0040102A cvtsi2ss xmm0,eax
0040102E add esp,8
00401031 prefix rep:
00401032 db 0F
00401033 pop edx
00401034 sal dl,0F
00401037 adc [esp],eax
0040103A push 004020F4 ; ASCII "%f",LF
0040103F call esi
00401041 add esp,0C
2009年12月12日 03点12分
13
level 13
嗯...float到int的转换确实是内联的,但double到int是ftol()
2009年12月12日 04点12分
14
level 9
double -> int也是内嵌的
你试试用libcmt而非msvcrt
2009年12月12日 05点12分
15
level 13
使用"多线程(静态库)"仍然不内联.
/O2 /Ob2 /Oi /Ot /Oy /GL /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /GF /FD /MT /GS- /arch:SSE2 /fp:fast /Fo"Release\\" /Fd"Release\vc80.pdb" /W3 /nologo /c /Wp64 /Zi /TP /errorReport:prompt
00401000 fld qword ptr [40F3B0]
00401006 sub esp,8
00401009 fstp qword ptr [esp]
0040100C push 0040F3A0 ; ASCII "%f",LF
00401011 call 0040104A ; printf()
00401016 push 0040F3A4 ; ASCII "-1.9"
0040101B call 0040123A ; atof()
00401020 call 0040D3D0 ; ftol()
00401025 cvtsi2ss xmm0,eax
00401029 add esp,8
0040102C prefix rep:
0040102D db 0F
0040102E pop edx
0040102F sal dl,0F
00401032 adc [esp],eax
00401035 push 0040F3A0 ; ASCII "%f",LF
0040103A call 0040104A ; printf()
0040103F add esp,0C
2009年12月12日 06点12分
16
level 13
这是VC2005的ftol():
0040D3D0 cmp dword ptr [411F0C],0
0040D3D7 je short 0040D406 ; 不支持SSE2就跳到普通版的ftol()
0040D3D9 push e
bp
0040D3DA mov ebp,esp
0040D3DC sub esp,8
0040D3DF and esp,FFFFFFF8
0040D3E2 fstp qword ptr [esp]
0040D3E5 prefix repne:
0040D3E6 cvttps2pi mm0,[esp]
0040D3EA leave
0040D3EB retn
2009年12月12日 06点12分
17
level 9
用的着OD吗?直接用VC就可以啦。
不过我建议你换一种写法
int a[10];
a[0] = (int)*(float*)(a+2);
a[1] = (int)*(doube*)(a+4);
printf("%d,%d\n",a[0],a[1]);
2009年12月12日 07点12分
19
level 13
确实很奇怪,atof()的返回值取整就调用了_ftol2_sse
2009年12月12日 07点12分
20