liangbch的专栏

浮点数到整数的快速转换

在计算机图形运算中,常常要将浮点数转换为整数,例如在图像的光栅化阶段,就要执行大量的类型转换,以便将浮点数表示的坐标转化为整数表示的屏幕坐标。

—————————————————————————————-

//

// 强制类型转换

// 小数部分将被裁剪掉

//

int_val = (int)float_val;

—————————————————————————————-

嘿嘿,很高兴你居然和我一样单纯!这个操作实在是太TINY了,以至于我从来没想过它是怎么实现的,直到某天某个家伙跟我说,不要使用标准C类型转换,因为那太慢了!我当时的震惊不下于倒霉的冒险者遇上了龙。

标准C类型转换最大的优点是,它是独立于平台的,无论是在X86上跑,还是在PowerPC上跑,你什么都不用担心,编译器会为你搞定一切。而这也恰恰是它最大的缺点——严重依赖于编译器的实现。而实际测试表明,编译器所生成的代码,其速度实在不尽人意。

一个替代的方法是直接对数据位进行操作。如果你对IEEE浮点数的表示法比较熟悉的话(如果你压根什么都不知道,请先查阅文末附录中的资料),这是显而易见的。它提取指数和尾数,然后对尾数执行移位操作。代码如下:

—————————————————————————————-

//

// 将32位浮点数fval转换为32位整数并存储在ival中

// 小数部分将被裁剪掉

//

void TruncToInt32 (int &ival, float fval)

{

ival = *(int *)&fval;

// 提取尾数

// 注意实际的尾数前面还有一个被省略掉的1

int mantissa = (ival & 0x07fffff) | 0x800000;

// 提取指数

// 以23分界,指数大于23则左移,否则右移

// 由于指数用偏移表示,所以23+127=150

int exponent = 150 – ((ival >> 23) & 0xff);

if (exponent < 0)

ival = (mantissa << -exponent);

else

ival = (mantissa >> exponent);

// 如果小于0,则将结果取反

if ((*(int *)&fval) & 0x80000000)

ival = -ival;

}

—————————————————————————————-

该函数有一个BUG,那就是当fval=0时,返回值是2。原因是对于语句mantissa>>exponent,编译器使用了循环移位指令。解决方法是要么对0作特殊处理,要么直接用汇编来实现。

这个函数比标准的C转换要快,而且由于整个过程只使用了整数运算,可以和FPU并行运行。但缺点是,(1)依赖于硬件平台。例如根据小尾和大尾顺序的不同,要相应地修改函数。(2)对于float和double要使用不同的实现,因为二者的数据位不同。(3)对于float,只能保留24位有效值,尽管int有32位。

更快的方法是使用FPU指令FISTP,它将栈中的浮点数弹出并保存为整数:

—————————————————————————————-

//

// 将64位浮点数fval转换为32位整数并存储在ival中

// 小数部分将四舍五入到偶数

//

inline void RoundToIntFPU (int &ival, double fval)

{

_asm

{

fld fval

mov edx, dword ptr [ival]

fistp dword ptr [edx]

}

}

—————————————————————————————-

很好,无论速度还是精度似乎都相当令人满意。但如果换一个角度来看的话,fistp指令需要6个cycle,而浮点数乘法才仅仅需要3个cycle!更糟的是,当fistp运行的时候,,它必须占用FPU,也就是说,其他的浮点运算将不能执行。仅仅为了一次类型转换操作就要付出如此大的代价,光想想就觉得心疼。

也不要说曾经失去,失去的不是永远失去,得到的不是永远拥有,

liangbch的专栏

相关文章:

你感兴趣的文章:

标签云: