【问题标题】:Should I use multiplication or division for recurring floats?我应该对重复的浮点数使用乘法还是除法?
【发布时间】:2016-02-28 23:55:24
【问题描述】:

众所周知,除法比乘法需要更多的时钟周期来计算。 (参考这里的讨论:Floating point division vs floating point multiplication。)

我已经在我的 C++ 代码中使用 x * 0.5 代替 x / 2x * 0.125 代替 x / 8,但我想知道我应该走多远。

对于反转时重复出现的小数(即1 / num 是重复出现的小数),我使用除法而不是乘法(例如x / 2.2 而不是x * 0.45454545454)。

我的问题是:在迭代次数相当多的循环中,我是否应该将除数替换为它们的重复乘法对应项(即x * 0.45454545454 而不是x / 2.2),还是这样带来更大的精度损失?

编辑:我做了一些分析,我在 Visual Studio 中开启了全面优化,使用 Windows QueryPerformanceCounter() 函数来获取分析结果。

int main() {
    init();
    int x;
    float value = 100002030.0;
    start();
    for (x = 0; x < 100000000; x++)
        value /= 2.2;
    printf("Div: %fms, value: %f", getElapsedMilliseconds(), value);
    value = 100002030.0;
    restart();
    for (x = 0; x < 100000000; x++)
        value *= 0.45454545454;
    printf("\nMult: %fms, value: %f", getElapsedMilliseconds(), value);
    scanf_s("");
}

结果为:Div:426.907185ms,值:0.000000 乘法:289.616415ms,值:0.000000

除法所用的时间几乎是乘法的两倍,即使经过优化也是如此。性能优势是有保证的,但会降低精度吗?

【问题讨论】:

  • 如果这是代码中的瓶颈,我会感到惊讶。你试过分析它吗?
  • 你会失去一些精度。如果它对您有帮助的话,这是一个非常小的优化。
  • 除法时间会随着使用的实际值而波动,也许乘法也会波动,因此为了公平比较,您应该从两个循环开始 value 相同。
  • 在没有任何上下文的情况下,编译器可以用一些关于无穷大、NaN 和负数的条件替换任何一个循环,然后是一个简单的value = 0.0f;

标签: c++ floating-point


【解决方案1】:

对于反转时重复出现的小数(即 1 / num 是重复出现的小数),我使用除法而不是乘法(例如 x / 2.2 而不是 x * 0.45454545454)。

众所周知,22/10 不能精确地用二进制浮点表示,因此您所要实现的不是乘以一个稍微不准确的值,而是除以一个稍微不准确的值。

事实上,如果目的是除以 22/10 或其他一些不一定能以二进制浮点数精确表示的实数值,那么乘法比除法更准确,因为它巧合的是,1/X 的相对误差小于 X 的相对误差。

另一个说明是,您的微基准运行到次正常数字,其中时间不代表正常浮点数的通常操作的时间,并且不久之后,value 为零,这再次意味着时间并不能代表正常数的乘法和除法的现实。正如 Mark Ransom 所说,您至少应该使两个测量的操作数相同:正如目前所写的那样,所有乘法运算都采用零操作数并导致零。此外,由于2.20.45454545454 都具有double 类型,因此您的基准是测量双精度乘法和除法,如果您愿意通过双精度乘法实现单精度除法,则needs not involve any loss of accuracy (但您必须为1/2.2 提供更多数字)。

但不要让自己上当尝试修复微基准。 您不需要它,因为当 X 不能比 1/X 更精确地表示时,没有任何权衡。没有理由不使用乘法。

注意:你应该显式地乘以1 / X,因为/ X* (1 / X) 这两个操作有很大的不同,编译器不能自己进行替换。另一方面,您不需要将/ 2 替换为* 0.5,因为任何值得其盐分的编译器都应该为您做到这一点。

【讨论】:

    【解决方案2】:

    当乘以倒数与除以时,您会得到不同的答案,但在实践中这通常并不重要,而且性能提升是值得的。最多,倒数乘法的误差为 1 ULP,而除法的误差为 ½ ULP。但是做

    a = b * (1.f / 7.f);
    

    而不是

    a = b * 0.142857f;
    

    因为前者将为 1/7 生成最准确的 (½ ULP) 表示。

    【讨论】:

      猜你喜欢
      • 2012-07-25
      • 2023-03-25
      • 2013-05-07
      • 2011-05-06
      • 2010-11-07
      • 2016-01-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多