我目前正在阅读优秀的Library for Double-Double and Quad-Double Arithmetic论文,在前几行我注意到他们通过以下方式执行总结:
std::pair<double, double> TwoSum(double a, double b)
{
double s = a + b;
double v = s - a;
double e = (a - (s - v)) + (b - v);
return std::make_pair(s, e);
}
错误的计算e
依赖于计算遵循该操作顺序的事实,因为IEEE-754浮点数学的非关联属性。
如果我在现代优化C ++编译器(例如MSVC或gcc)中编译它,我是否可以确保编译器不会优化计算方式?
其次,这在C ++标准中是否可以保证?
答案 0 :(得分:6)
您可能希望查看g ++手册页:http://gcc.gnu.org/onlinedocs/gcc-4.6.1/gcc/Optimize-Options.html#Optimize-Options
特别是-fassociative-math,-ffast-math和-ffloat-store
根据g ++手册,除非您特别要求,否则不会重新排序您的表达式。
答案 1 :(得分:5)
这是一个非常有效的问题,因为英特尔的C ++编译器(使用非常广泛)默认执行可以改变结果的优化。
答案 2 :(得分:4)
是的,这是安全的(至少在这种情况下)。你只在那里使用两个“运算符”,主表达式(something)
和二进制something +/- something
(添加剂)。
部分1.9 Program execution
(C ++ 0x N3092)声明:
操作符可以按照通常的数学规则重新分组,只有操作符真正是关联的或可交换的。
就分组而言,5.1 Primary expressions
州:
带括号的表达式是一个主表达式,其类型和值与所附表达式的类型和值相同。 ...除非另有说明,否则带括号的表达式可以与可以使用封闭表达式的上下文完全相同,并且具有相同的含义。
我认为在引用中使用“相同”一词需要一致的实现,以保证它将以指定的顺序执行,除非另一个订单可以给出 exact 相同的结果。
对于添加和减去,第5.7 Additive operators
部分包含:
加法运算符+和 - 从左到右分组。
因此标准规定了结果。如果编译器可以确定可以通过不同的操作顺序获得相同的结果,那么它可以重新排列它们。但无论这种情况发生与否,你都无法辨别出差异。
答案 3 :(得分:2)
如果任何编译器错误地假设算术运算符与默认优化选项相关联,我会感到非常惊讶。
但要注意FP寄存器的扩展精度。
请参阅有关如何确保FP值没有扩展精度的编译器文档。
答案 4 :(得分:0)
通常,您应该能够 - 优化器应该了解实际操作的属性。
那就是说,我要测试我正在使用的编译器。
答案 5 :(得分:0)
是。编译器不会像这样更改块内的计算顺序。
答案 6 :(得分:0)
在处理器上的编译器优化和乱序执行之间,几乎可以保证事情不会像您订购的那样完全发生。
但是,也保证不会改变结果。 C ++遵循标准的操作顺序,所有优化都保留了这种行为。
底线:不要担心。编写C ++代码以在数学上正确并信任编译器。如果出现任何问题,问题几乎肯定不是编译器。
答案 7 :(得分:0)
如果你真的需要,我想你可以制作一个noinline函数no_reorder(float x){return x;然后使用它而不是括号。显然,它不是一个特别有效的解决方案。
答案 8 :(得分:-1)
根据其他答案,您应该能够依赖编译器做正确的事情 - 大多数编译器允许您编译和检查汇编程序(使用-S表示gcc) - 您可能希望这样做确定你得到了你期望的操作顺序。
不同的优化级别(在gcc中,-O _O2等)允许重新排列代码(但是这样的顺序代码不太可能受到影响) - 但我建议你应该将代码的特定部分隔离到一个单独的文件,以便您可以控制优化级别,仅用于计算。
答案 9 :(得分:-1)
简短的回答是:编译器可能会改变计算的顺序,但它永远不会改变程序的行为(除非你的代码使用带有未定义行为的表达式:http://blog.regehr.org/archives/213)
但是,您仍然可以通过停用所有编译器优化来影响此行为(使用gcc选项“-O0”)。如果您仍然需要编译器来优化代码的其余部分,您可以将此函数放在单独的“.c”中,您可以使用“-O0”进行编译。 此外,您可以使用一些黑客。例如,如果您将代码与extern函数调用交错,编译器可能会认为重新排序代码是不安全的,因为该函数可能具有未知的副作用。调用“printf”来打印中间结果的值将导致类似的行为。
无论如何,除非你有任何理由(例如调试),否则你通常不想关心它,你应该相信编译器。