假设我有一个非常小的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但找不到任何内容。
是否有一种在浮点运算中对操作数进行排序的最佳方法?
答案 0 :(得分:6)
这实际上取决于价值观和目标。例如,如果a
非常小,a*a
可能为零,而6000.0*a*a
(这意味着(6000.0*a)*a
)仍可能为非零。为了避免上溢和下溢,一般规则是应用关联法则首先执行乘法,其中操作数的日志具有相反的符号,这意味着先行平方通常是最差的策略。另一方面,出于性能原因,如果可以重用平方的值,则首先平方可能是一个非常好的策略。您可能会遇到另一个问题,如果您的数字永远不会非常接近于零或无穷大,那么对于正确性而言可能比溢出/下溢问题更重要:某些乘法可以保证具有精确答案,而其他则涉及舍入。通常,通过最小化发生的舍入步骤数量,您将获得最准确的结果。
答案 1 :(得分:3)
通常不是,不。
话虽这么说,如果你正在使用大值进行多个操作,可能有意义地以避免溢出或减少精度错误的方式对它们进行排序,基于它们{{3} },如果算法提供了一种方法,使这一点变得明显。但是,这需要事先了解所涉及的值,而不仅仅是基于语法。
答案 2 :(得分:3)
最佳方式取决于目的,真的。
首先,乘法比除法快。
因此,如果你必须写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
,只需在编写时编写,无需更改;这很好。
答案 3 :(得分:2)
确实有一些算法可以最大限度地减少浮点运算序列中的累积误差。其中之一是http://en.wikipedia.org/wiki/Kahan_summation_algorithm。其他操作存在其他操作:http://www.cs.cmu.edu/~quake-papers/related/Priest.ps。