Delphi中提供了三个函数:
Trunc: 将浮点数的整数部分返回。
Round: 将浮点数四舍五入后返回整数部分。
Int: 将浮点数的小数部分去掉,返回只保留了整数部分的Extended型。
在D2007 + Win7 + Intel平台下测试, 由于Int返回的并非整型,我们把它排除掉。测试结果是 Round 性能高出Trunc好几倍。
为什么Trunc慢了呢,查看了System中的ASM代码,Trunc有11行指令,Round则只有5行。
出于好奇,通过网络研究下Trunc的算法,知道了在 IEEE 754 里规定双精度浮点数是 64 位,而其中符号位占 1 位,指数部分是 11 位,那么尾数部分就是 52 位,那么 1 * 2^52 = $10000000000000。设要取整的浮点数为V, 那么通过符点数运算的对阶法, PInteger(V + $10000000000000 的结果)^ 就是计算结果了。
于是有了下面的函数:
function MyTrunc(const V: Double): Integer; inline; var D: Double; begin if V > 0 then D := V - 0.499999999999 + $18000000000000 else D := V + 0.499999999999 + $18000000000000; Result := PInteger(@D)^; end;
再来测试看看:
procedure TForm1.Button6Click(Sender: TObject); const C = 100000000; var u: Extended; I: Integer; v, v1, v2: Integer; t, t1, t2: Int64; begin u := Now; t := GetTimestamp; for I := 0 to C - 1 do v := Trunc(u); t := GetTimestamp - t; t1 := GetTimestamp; for I := 0 to C - 1 do v1 := Round(u); t1 := GetTimestamp - t1; t2 := GetTimestamp; for I := 0 to C - 1 do v2 := MyTrunc(u); t2 := GetTimestamp - t2; ShowMessage(Format('Trunc: %dms, Round: %dms, MyTrunc: %dms'#13'%d, %d, %d', [t, t1, t2, v, v1, v2])); end;
// 注: GetTimestamp 为高精度计时器返回单位为ms(与QWorker中的返回0.1ms有所不同),可换成GetTickCount。
测试结果如下:
哈哈,居然成了最快的了。
要注意的是, MyTrunc 我这里并没有像System的Trunc那样写汇编代码,一是那样不能inline了,二是通过观测编译结果发现已经是很简单的几行指令了。
通过对MyTrunc简单个修改下,成了MyRound:
function MyRound(const V: Double): Integer; inline; var D: Double; begin D := V + $18000000000000; Result := PInteger(@D)^; end;
参考资料:
http://www.360doc.com/content/14/0308/22/3520047_358884576.shtml