【问题标题】:Is there a preferred way to order floating-point operands?有没有首选的方式来订购浮点操作数?
【发布时间】:2011-10-28 22:48:03
【问题描述】:

假设我有一个很小的float a(例如a=0.5),它输入以下表达式:

6000.f * a * a;

操作数的顺序有什么不同吗?是不是比较好写

6000.f * (a*a);

甚至

float result = a*a;
result *= 6000.f;

我检查了经典的What Every Computer Scientist Should Know About Floating-Point Arithmetic,但找不到任何东西。

在浮点运算中是否有最佳的操作数排序方式?

【问题讨论】:

    标签: c floating-point


    【解决方案1】:

    这真的取决于价值观和你的目标。例如,如果a 非常小,则a*a 可能为零,而6000.0*a*a(这意味着(6000.0*a)*a)仍可能为非零。为了避免上溢和下溢,一般规则是应用结合律首先执行操作数的对数符号相反的乘法,这意味着首先平方通常是最差的策略。另一方面,出于性能原因,如果您可以重用平方的值,则先平方可能是一个非常好的策略。您可能会遇到另一个问题,如果您的数字永远不会非常接近零或无穷大,那么这对于正确性可能比上溢/下溢问题更重要:某些乘法可以保证有准确的答案,而其他乘法则涉及四舍五入。一般来说,通过最小化舍入步骤的数量,您可以获得最准确的结果。

    【讨论】:

    • "operands'logs have contrast sign"表示其中一个操作数>1,另一个
    【解决方案2】:

    实际上,最佳方式取决于目的。

    首先,乘法比除法快。

    所以如果你必须写a = a / 2;,最好写a = a * 0.5f;。 如果结果相同,您的编译器通常足够聪明,可以用乘法代替除法,但它当然不会对变量这样做。

    有时,您可以通过将除法替换为乘法来进行一些优化,但可能存在精度问题。

    其他一些操作可能更快但不太精确。 举个例子吧。

    float f = (a * 100000) / (b * 10);
    float g = (a / b) * (100000 / 10);
    

    这些在数学上是等价的,但结果可能会有所不同。 第一个使用二乘一除,第二个使用一除一乘。在这两种情况下都可能会损失精度,这取决于 a 和 b 的大小,如果它们是小值,首先效果更好,如果它们是大值,则效果更好

    然后...如果您有多个常量并且想要速度,请将常量组合在一起。

    float a = 6.3f * a * 2.0f * 3.1f;
    

    随便写

    a = a * (6.3f * 2.0f * 3.1f);
    

    有些编译器优化得很好,有些编译器优化得不太好,但在这两种情况下,将所有常量放在一起是没有风险的。

    说完这些,我们应该花几个小时讨论处理器的工作原理。 即使是像英特尔这样的同一个家族,世代之间的工作方式也不同! 有些编译器使用 SSE 指令,有些则不使用。 一些处理器支持 SSE2,一些 SSE,一些只支持 MMX……一些系统也没有 FPU! 每个系统在某些计算上都比其他系统做得更好,很难找到一个共同点。

    你应该只写一段可读的代码,简洁明了,不用太担心这些不可预测的非常低级的优化。

    如果您的表达式看起来很复杂,请做一些代数并\或去 wolframalpha 搜索引擎并请他为您优化:)

    也就是说,你真的不需要声明一个变量并一遍又一遍地替换它的内容,编译器通常可以在这种情况下进行较少的优化。

    a = 5 + b;
    a /= 2 * c;
    a += 2 - c;
    a *= 7;
    

    只需写下你的表达方式,避免这种混乱:)

    a = ((5 + b) / (2 * c) + 2 - c) * 7;
    

    关于你的具体例子,6000.f * a * a,随你写,不用改;没关系。

    【讨论】:

    • 编译器不能用除法代替乘法,除非结果完全相同,这种情况很少见。它只能在二次幂的情况下真正发生。 (但是,GCC 上的 -ffast-math 将启用此类优化。)
    • 我在谈论用常量进行除法,如果这样做没有风险,微软视觉 C 会这样做。它取代了 x /= 2; x *= 0.5f;从各个角度来看,这都是安全的。
    • 正如我所说,它只能发生在二次方的情况下。所以x / 3不能改成乘法(除非x是整数,当然……)
    • 是的,当然,二的幂可以用浮点数编码而不会丢失精度,其他类型的小数可能不是,但我想这取决于编译器,正如你在优化设置。 Microsoft Visual C 有一个标志,询问您是否需要精确或快速的浮点运算。
    • 出于与除法->乘法转换相同的原因,编译器无法将6.3f * a * 2.0f * 3.1f 转换为a * (6.3f * 2.0f * 3.1f),因为它不是同一个东西。在没有程序员提示的情况下进行这种转换的编译器不是优化编译器,而是错误的编译器。我同意程序员可以自己进行转换,这对他来说通常没问题,但编译器不这样做的原因不是缺乏优化。
    【解决方案3】:

    通常不会,不会。

    话虽如此,如果您正在使用大值执行多个操作,可能根据它们的precedence and associativity,以一种避免溢出或减少精度错误的方式对它们进行排序是有意义的,如果该算法提供了一种使这一点显而易见的方法。但是,这需要提前了解所涉及的值,而不仅仅是基于语法。

    【讨论】:

    • 加上在现代处理器上,你真的不知道操作的执行顺序是什么,或者即使它们将被串行执行。
    • @MartyTPS:无关紧要。处理器计算出与串行执行操作相同的结果。
    • 注意,迪特里希。发布的问题是更改订单是否相关。它最终不一定在你的控制之下,所以是的,它是无关紧要的
    • @MartyTPS:你错了。订单在您的控制之下。编译器/cpu 不能以任何会改变结果的方式重新排序浮点运算,并且对于大多数目的,这意味着它们根本无法重新排序浮点数。
    • @MartyTPS 告诉你现代处理器中的乱序执行的人意味着独立指令可能以不同于指令流的顺序执行。处理器不会重新排序相关指令。 en.wikipedia.org/wiki/Out-of-order_execution
    【解决方案4】:

    确实有一些算法可以最小化一系列浮点运算中的累积误差。其中之一是http://en.wikipedia.org/wiki/Kahan_summation_algorithm。其他操作存在:http://www.cs.cmu.edu/~quake-papers/related/Priest.ps

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-12-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-11-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多