浮点数的精度和转换

浮点数的精度和转换

浮点数的精度和转换

float(24bits,有效位数,不包括指数部分和符号位,下同)和double(53bits)类型,指的是浮点数在内存中存储精度。而在FPU中,却存在着三种运算精度:single precision(24bits),double precision(53bits),double extended precision(64bits)。FPU的默认精度是53bits的double precision。D3D的CreateDevice函数会将FPU的运算精度改成24bits,除非指定了D3DCREATE_FPU_PRESERVE参数

除了运算精度之外,FPU的Control Word中还有一个叫做RC的字段,控制浮点转整型的转换方式,共有四种转换方式:

1. round to nearest(even) 取整后的差值最小,即四舍五入方式,如前后的差值相等则取向偶数,如3.5则取得偶数4,2.5则取得偶数2。这是FPU的默认取整方式。

2. round down 向负无穷大方向取整(ceil函数),如3.5取得3,-3.5取得-4

3. round up 向正无穷大方向取整(floor函数),如3.5取得4,-3.5取得-3

4. round toward zero(truncate) 向零的方向取整(浮点数转整数),如3.5取得3 ,-3.5取得-3

一个32位的整数,如果转成float型存储在内存中,就有可能导致误差,因为float只有24位有效位;而转成double型存储在内存中,则不会导致误差,但是当FPU的运算精度为single precision时,一旦通过FPU进行了运算,就有可能导致误差。64位的int在double precision时也有类似的问题存在,因此猜想64位CPU上FPU的默认运算精度应该是double extended precision才对。

游戏中,由于考虑到D3D的性能,我们的浮点运算精度是24bits的。同时,Lua的内部没有区分整型和浮点型,而统一采用了double类型来存储数值。如果仅仅是存储32bits的整型数据,那么不会造成任何问题。但是,一旦需要进行运算,则支持的整数范围其实已经降到了-2^24至2^24。

接下来分析以下6种转换过程:

1. dword->int 直接复制内存

2. int->dword 直接复制内存

3. dword->double

mov eax,dword ptr [dwNumber]
mov dword ptr [ebp-134h],eax
mov dword ptr [ebp-130h],0
fild qword ptr [ebp-134h]
fstp qword ptr [fNumber]

4. double->dword

fld qword ptr [fNumber]
fnstcw word ptr [ebp-12Eh] //*
movzx eax,word ptr [ebp-12Eh]
or eax,0C00h
mov dword ptr [ebp-134h],eax
fldcw word ptr [ebp-134h] //*这段将取整方式切换为向零取整
fistp qword ptr [ebp-13Ch]
fldcw word ptr [ebp-12Eh]
mov eax,dword ptr [ebp-13Ch]
mov dword ptr [dwNumber],eax

5. int->double

fild dword ptr [nNumber]
fstp qword ptr [fNumber]

6. double->int

fld qword ptr [fNumber]
call @ILT+215(__ftol2_sse) (4110DCh)
mov dword ptr [dwNumber],eax

fld指令从内存中将一个4字节(single)、8字节(double)、10字节(double extended)的浮点数压入FPU的浮点寄存器栈中

fstp指令从FPU的浮点寄存器栈中弹出一个浮点数,依据目标内存空间的大小转换成对应的精度,存入指定的内存地址

fild指令从内存中将一个2字节(word)、4字节(dword)或8字节(qword)带符号整数转换成一个浮点数并压入FPU的浮点寄存器栈

fistp指令从FPU的浮点寄存器栈中弹出一个浮点数,并转换成2字节(word)、4字节(dword)或8字节(qword)带符号整数存入指定的内存地址;在这个转换过程中,当浮点数过大时,如果control word中的invalid operation位被置0,则触发异常(Unhandled exception at 0x00411799 in test.exe: 0xC0000090: Floating-point invalid operation.),且不会向目标内存中存入任何数值;如果被置1(默认为1),则屏蔽异常,同时向目标内存中存入一个表示无限大的整数值。例如当目标内存为dword时,过大的负数会被转化成-2^31,而过大的整数会被转换成2^31。

__ftol2_sse函数在pentium机器上,会利用sse中的cvttsd2si指令,将一个double precsion的浮点数转换成一个带符号的32位整数,采用向零取整(truncate)方式。

由于浮点数操作指令中的整型数值都是带符号的,因此3号和4号转换中的无符号整数需要特殊处理

如dword->int->double->dword的转换不会导致任何问题,而dword->double->int->dword的转换中double->int就会因为数值过大而触发异常,或者存入了无限大整数值而导致错误

再来看看6种转换的效率:

1. dword->int 1(CPU周期,下同)

2. int->dword 1

3. dword->double 4

4. double->dword 56

5. int->double 0

6. double->int 26

测试环境:迅驰1.7G,VS2005,RDSTC空转耗时234个周期,以上数字已经减去该时间

由于流水线存在并行计算,所以5号转换的时间没有表现出来,而且所有周期数并不是实际消耗的周期数,但是反应出的消耗大小关系应该是正确的。

运算速度的测试结果:

float(24) float(53) double(24) double(53)
+ 5 5 5 5
- 5 5 5 5
* 5 5 8 8
/ 17 31 17 31
sin 805 610 390 170
sqrt 720 510 290 80

double类型加上默认运算精度,是FPU运算速度最快的组合。至于为什么D3D要求float(24)的组合,可能和显卡的带宽有关系,毕竟会使得带宽占用减少一半,而这很可能是显卡的主要性能瓶颈。不知道咋测试显卡相关的性能指标,随便猜测一下罢了。

附:切换control word的代码

WORD temp;

__asm //打开invalid operation异常
{
fnstcw word ptr [temp]
mov ax,word ptr [temp]
and ax,0FCFEh
or ax,3Eh
and ax,0F3FEh
mov word ptr [temp],ax
fldcw word ptr [temp]
};

__asm //切换到single precision运算精度 { fnstcw word ptr [temp] mov ax,word ptr [temp] and ax,0FCFFh or ax,3Fh and ax,0F3FFh mov word ptr [temp],ax fldcw word ptr [temp] };

免责声明:本网信息来自于互联网,目的在于传递更多信息,并不代表本网赞同其观点。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,并请自行核实相关内容。本站不承担此类作品侵权行为的直接责任及连带责任。如若本网有任何内容侵犯您的权益,请及时联系我们,本站将会在24小时内处理完毕。
相关文章
返回顶部