浮点格式以及一些技巧
浮点数据格式:
struct Sfloat {
unsigned int fraction : 23;
unsigned int exponent : 8;
unsigned int sign : 1;
};
struct Sdouble {
unsigned int fraction : 52;
unsigned int exponent : 11;
unsigned int sign : 1;
};
struct Slongdouble {
unsigned int fraction : 63;
unsigned int one : 1;
unsigned int exponent : 15;
unsigned int sign : 1;
};
现在的x86处理器都有两套浮点寄存器以及相应的两套浮点指令(传统的8个浮点寄存器组,以及16个或者8个XMM寄存器),XMM寄存器不支持long double。如果可以使用XMM寄存器大多数编译器不会使用浮点寄存器组。
对于浮点寄存器组,大多数情况下float以及double的运算耗时相同,long double消耗略微多一点时间。浮点数据的比较操作以及float与double之间的转换效率不高。
对于XMM寄存器,加减乘除等简单运算float与double耗时相同,但是做除法,开方等数学运算时float比double快。但是不要混用float以及double,这类转换通常很耗时。
总的来说,使用double不会带来性能损失,但是当你需要定义大型数据是,选择较小的数据类型仍然是很必要的,因为这样才能尽可能多的把数据放到处理器缓存中。另外,使用float可以更有效的利用XMM指令集的单指令多数据优势。
浮点加法(减法)通常耗时3--6个时钟周期,乘法通常耗时4--8个时钟周期,除法通常耗时32--45个时钟周期(耗时随处理器不同而不同)。通常写代码时因该考虑能否将浮点常量的除法转化成它的倒数的乘法,编译器会在编译时计算常量表达式的值(有些编译器可以自动转换,但要求打开相应的优化选项),通常这些应该手动完成(不是什么难事)。另外,通常可以考虑将表达式通分来减少除法操作的数量。
Example 1:
double a,b,c,d,x,y;
..........//initialize the variables here before using them
x= a/b;
y= c/d;
可以用下面的方法实现
Example 2:
double a,b,c,d,x,y,reciprocal;
.......... //initialize the variables here before using them
reciprocal = 1 / (b * d);
x= a * d * reciprocal;
y = c * b * reciprocal;
C++标准要求浮点到整型的转换使用truncation而非rounding,对于浮点寄存器组,前者要比后者慢很多,必要时可以自己实现一个rounding的转换函数(使用fistp指令或者使用SSE2的_mm_cvtss_si32和_mm_cvtsd_si32这两个intrinsic函数,另外,有些编译器提供了lrintf 和lrint则会两个函数实现rounding操作)。
最后,可以使用整数的位运算来对浮点类型的数据进行操作。
Example 3:
float f,a,b;
int n;
.......... //initialize the variables here before using them
//符号位清零
*(int*)&f &= 0x7FFFFFFF;
//判断是否是0
if (*(int*)&f & 0x7FFFFFFF) { // 测试0--30位的元素
// f is nonzero
}
else {
// f is zero
}
//乘以2的n次方(不检查溢出,只能是正数)
if (*(int*)&f & 0x7FFFFFFF) { // 是0吗?
*(int*)&f += n << 23; // 加到指数上
}
if (*(int*)&a > *(int*)&b) {
// a > b ,要求a.b为正数
}
if (*(unsigned int*)&a * 2 > *(unsigned int*)&b * 2) {
// |a| > |b|
}
搜索更多相关主题的帖子:
优化