Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/c/72.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 双精度高性能加法和乘法的常数形式_C++_C_Optimization_Underflow - Fatal编程技术网

C++ 双精度高性能加法和乘法的常数形式

C++ 双精度高性能加法和乘法的常数形式,c++,c,optimization,underflow,C++,C,Optimization,Underflow,我需要在循环中有效地向double类型的结果添加或乘以一些常量,以防止下溢。例如,如果我们有int,那么与2的幂相乘会很快,因为编译器将使用位移位。有效的double加法和乘法是否有一种常量形式 编辑:似乎没有多少人理解我的问题,为我的草率道歉。我将添加一些代码。 如果a是整数,则该值(乘以2的幂)将更有效 int a = 1; for(...) for(...) a *= somefunction() * 1024; 当1024被替换为1023时。不确定如果我们想添加

我需要在循环中有效地向double类型的结果添加或乘以一些常量,以防止下溢。例如,如果我们有int,那么与2的幂相乘会很快,因为编译器将使用位移位。有效的double加法和乘法是否有一种常量形式

编辑:似乎没有多少人理解我的问题,为我的草率道歉。我将添加一些代码。 如果
a
是整数,则该值(乘以2的幂)将更有效

int a = 1;
for(...)
    for(...)
        a *= somefunction() * 1024;
当1024被替换为1023时。不确定如果我们想添加一个int,什么是最好的,但这不是我感兴趣的。我对
a
是双精度的情况感兴趣。什么是常数的形式(例如2的幂),我们可以有效地将相乘为双精度?常数是任意的,只需要足够大以防止下溢


这可能不限于C和C++,但我不知道有什么更合适的标记。

首先在一个并集中获得双倍,选择<强>范围> <强> >强>“指数”<强>部分。然后只移动“指数”或“范围”部分。寻找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;

您可以使用标准frexp/ldexp函数将IEE 754值分解为其组件:

下面是一个简单的示例代码:

#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;
}
#包括
#包括
int main()
{
双值=5.4321;
整数指数;
双有效位=frexp(值和指数);
双结果=ldexp(有效位,指数+1);

std::cout在大多数现代处理器上,简单地乘以二的幂(例如,
x*=0x1p10;
乘以210或
x*=0x1p-10;
除以210)将快速且无错误(除非结果大到足以溢出或小到足以下溢)

有些处理器具有“提前退出”功能对于某些浮点运算。也就是说,当某些位为零或满足其他条件时,它们会更快地完成指令。然而,浮点加法、减法和乘法通常在大约四个CPU周期内执行,因此即使没有早出,它们也相当快。此外,大多数现代处理器执行几个每次执行1条指令,因此在乘法过程中,其他工作会继续进行,并且它们是流水线的,因此,通常每个CPU周期可以启动(和完成)一次乘法(有时更多)

乘以二的幂没有舍入误差,因为有效位(值的分数部分)没有变化,所以新的有效位是可以精确表示的。(除非乘以小于1的值,否则有效位的位可能会被推到低于浮点类型的限制,从而导致下溢。对于常见的IEEE 754双精度格式,在该值小于0x1p-1022之前不会发生这种情况。)


请勿使用除法进行缩放(或反转先前缩放的效果)。相反,请乘以反比。(要删除先前0x1p57的缩放,请乘以0x1p-57。)这是因为大多数现代处理器上的除法指令都很慢。例如,30个周期并不罕见。

在千兆赫兹处理器上,通过优化这种方式(移位与算术)可以节省1或2纳秒。但是,从内存加载和存储所需的时间约为100纳秒,从磁盘加载和存储所需的时间为10毫秒。与优化缓存使用率和磁盘活动相比,担心算术运算是毫无意义的。这在任何实际生产程序中都不会产生任何影响


为了避免误解,我不是说差异很小,所以不用担心,我是说它是零。你不能编写一个简单的程序,其中ALU时间的差异与CPU暂停等待内存或I/O的时间不完全重叠。

浮点加法和乘法通常需要f现代处理器中的电子战周期

也许你应该退一步想想算法在做什么。在你的例子中,你有一个双嵌套循环……这意味着“somefunction()”可能会被多次调用。“double”的常见表示形式是IEEE,它使用11位表示指数,52位表示尾数(53真的是因为除了零,还有一个隐含的“1”)。这意味着你可以在从非常小到非常大的数字范围内表示53位精度的数字-二进制“浮点”可以将1024(2^10)个位置移动到数字“1.0”的左或右……如果“somefunction()”被调用一千次,它总是返回一个小于或等于0.5的数字(每次乘以0.5,你将数字“a”减半,这意味着你将二进制浮点移到左边。在x86上,你可以告诉处理器“将非规范化刷新为零”通过在控制寄存器中设置一个位,没有可移植的编程接口来实现这一点,而您使用的是gcc

_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
告诉处理器将非规范化刷新为零将使代码运行更快,因为处理器不会尝试表示超出(小于)法线(次法线,或非规范化)的数字。面对生成次法线的算法(这会导致精度损失),您似乎在试图保持精度。如何最好地处理此问题取决于您是否控制“somefunction()”。如果您确实控制了该函数,则可以将其返回的值“规范化”为范围内的某个值

0.5 <= X <= 2.0

0.5如果使用SSE,直接向指数字段添加常数是一个合法的技巧(在FPU代码中,这非常糟糕)-它通常具有两倍的吞吐量和4倍的延迟(除了在具有浮点->int和/或int->