【Delphi】玩转浮点数转整数

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。

测试结果如下:

QQ截图20150619103148

 

 

 

 

 

哈哈,居然成了最快的了。

要注意的是, 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

 

分享到: