我需要在循环中有效地将一些常量添加或乘以double类型的结果以防止下溢。例如,如果我们有int,则乘以2的幂会很快,因为编译器将使用位移。是否存在一种有效double
加法和乘法的常量形式?
编辑:似乎没有多少人理解我的问题,为我的邋iness道歉。我会添加一些代码。
如果a
是一个int,那么(乘以2的幂)将更有效
int a = 1;
for(...)
for(...)
a *= somefunction() * 1024;
比1024更换为1023时。如果我们想要添加到int中,不确定什么是最好的,但这不是我感兴趣的。我对a
是双重的情况感兴趣。什么是形式的常数(例如2的幂)我们可以有效地将和乘以一个加倍?常量任意,只需要足够大以防止下溢。
这可能不仅限于C和C ++,但我不知道更合适的标签。
答案 0 :(得分:4)
在大多数现代处理器上,只需乘以2的幂(例如x *= 0x1p10;
乘以2 10 或x *= 0x1p-10;
除以2 10 < / sup>)将是快速且无错误的(除非结果大到足以溢出或小到足以下溢)。
对于某些浮点运算,有些处理器具有“早期出局”。也就是说,当某些位为零或满足其他标准时,它们会更快地完成指令。但是,浮点加法,减法和乘法通常在大约四个CPU周期内执行,因此即使没有提前输出它们也相当快。此外,大多数现代处理器一次执行多条指令,因此在发生乘法时会继续执行其他工作,并且它们是流水线的,因此通常可以在每个CPU周期中启动一次乘法(并且一次完成)。 (有时更多。)
乘以2的幂没有舍入误差,因为有效数(值的小数部分)不会改变,因此新的有效数是完全可表示的。 (除非乘以小于1的值,有效位的位数可以低于浮点类型的限制,导致下溢。对于常见的IEEE 754双格式,直到值小于0x1p-1022。)
不要使用除法进行缩放(或用于反转先前缩放的效果)。相反,乘以逆。 (要删除先前的0x1p57缩放,请乘以0x1p-57。)这是因为除法指令在大多数现代处理器上都很慢。例如,30个周期并不罕见。
答案 1 :(得分:2)
首先在联合中获得双倍并选择“范围”和“指数”部分。然后只移动“指数”或“范围”部分。寻找IEEE浮点标准。 不要忘记标志和最后一个尾数位。
union int_add_to_double
{
double this_is_your_double_precision_float;
struct your_bit_representation_of_double
{
int range_bit:53;//you can shift this to make range effect
//i dont know which is mantissa bit. maybe it is first of range_bit. google it.
int exponent_bit:10; //exponential effect
int sign_bit:1; //take negative or positive
}dont_forget_struct_name;
}and_a_union_name;
答案 2 :(得分:2)
浮点加法和乘法通常在现代处理器中需要几个周期。
也许您应该退一步思考算法正在做什么。在你的例子中,你有一个双嵌套循环......这意味着“somefunction()”可能被多次调用。 “double”的常见表示是IEEE,它使用11位作为指数,52位作为尾数(53实际上因为除零之外有一个隐含的'1')。这意味着您可以在从非常小到非常大的数字范围内将数字表示为53位精度 - 二进制“浮点”可以在数字“1.0”的左侧或右侧移动1024(2 ^ 10)个位置。 ..如果“somefunction()”被称为千次并且它总是返回一个小于或等于0.5的数字你下溢(每次乘以0.5你将你的数字“a”减少一半,这意味着你移动二进制浮动在x86上,您可以通过在控制寄存器中设置一个位来告诉处理器“将非正规数刷新为零” - 没有可执行此操作的可移植编程接口,使用gcc
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
告诉处理器将非正规数刷新为零会使代码运行得更快,因为处理器不会尝试表示超出(小于)法线(子法线或非正规)的数字。看起来你正试图在生成子法线的算法面前保持精度(这会导致精度损失)。如何最好地处理这取决于你是否控制“somefunction()”。如果您确实控制了该函数,那么您可以将其返回的值“规范化”到
范围内的值0.5 <= X <= 2.0
换句话说,返回值以1.0为中心,并分别跟踪2的幂,你需要将最终答案相乘以正确缩放它。
答案 3 :(得分:1)
如果您正在使用SSE,将常量直接添加到exponent字段是一个合法的技巧(在FPU代码中它非常糟糕) - 它通常具有两倍的吞吐量和4倍的延迟(除了具有浮点数的处理器) - &gt; int和/或int-&gt;浮点数)。但是既然你只是这样做是为了防止非正规,为什么不打开FTZ(刷新到零)和DAZ(非正规为零)?
答案 4 :(得分:0)
您可以使用标准的frexp / ldexp函数将IEE 754值分解为其组件:
http://www.cplusplus.com/reference/clibrary/cmath/frexp/
http://www.cplusplus.com/reference/clibrary/cmath/ldexp/
以下是一个简单的示例代码:
#include <cmath>
#include <iostream>
int main ()
{
double value = 5.4321;
int exponent;
double significand = frexp (value , &exponent);
double result = ldexp (significand , exponent+1);
std::cout << value << " -> " << result << "\n";
return 0;
}
答案 5 :(得分:0)
在千兆赫兹处理器上,您可以通过优化这种方式(移位与算术)来节省1或2纳秒。但是,从内存加载和存储所需的时间大约为100 nsecs,而磁盘则为10 msecs。与优化缓存使用和磁盘活动相比,担心算术运算毫无意义。它永远不会对任何真正的生产计划产生影响。
为了防止误会,我不是说差别很小所以不要担心,我说的是零。你不能编写一个简单的程序,其中ALU时间的差异与CPU等待内存或I / O的时间不完全重叠。