Oracle® Developer Studio 12.5:数值计算指南

退出打印视图

更新时间: 2016 年 6 月
 
 

5.5 可再现结果

如《数值计算指南》的 D.11 节中所述,即使符合标准的 IEEE 运算实现也可能会生成不同的结果。通常这些结果都同样有效,但是证明这种情况一般会很繁琐或非常困难。出于多种原因,最好是牺牲一些性能以减少验证结果所需的误差分析数量。输出中的细微差异或重大差异是否同样有效并不明确,而这些差异是由用户程序错误、编译器优化错误还是硬件错误造成的也不明确。

导致 IEEE 浮点运算的结果不同有几种主要根本原因。下面列出了这些原因并描述了一些方法,以便减少在不同发行版之间以及支持的 Oracle Developer Studio 平台之间出现的这种无故变化。请注意,每种方法在增加可再现性的同时,也有可能会降低性能。有时候性能下降会非常明显。

5.5.1 超越函数

大部分常用数学库函数(例如指数、对数和三角函数)按编程语言进行标准化,相比合理运算或者代数函数(例如平方根 sqrt()),正确舍入的成本非常高昂。接近正确的舍入函数适用于大多数用途,并且速度要快得多。但是,最快的接近正确舍入函数在不同的平台上也不同。

  • 对于由应用程序使用的函数,请使用可移植代码。此类代码的一个来源是免费分发的数学库 fdlibm。这个库可从 Netlib 软件系统信息库获取。

  • 避免使用 –xvector 选项。超越函数的向量化版本针对特定平台进行了优化,在不同平台上会得出略微不同的结果。

  • 避免使用 x86 硬件超越指令。即使这些指令具有小得不能再小的误差界限,也很可能无法正确舍入。此外,即使 Intel 和 AMD 的效果都非常好,但这两个版本偶尔会不同。借助 Oracle Developer Studio C/C++ 编译器,可以使用 –xbuiltin=%default(特别是在 –fast 之后)来确保编译器不会使用内置超越函数来内联取代任何超越指令。与此类似,–fast 之后的 –xnolibmil 选项禁用内联模板;Oracle Developer Studio 中的 libm.il 可能会有一些调用超越指令的模板。

5.5.2 结合运算

在实数运算中,加法和乘法属于结合运算,总和与乘积可按任意顺序计算。但是,存在舍入时,计算的顺序会影响计算答案。

  • 避免使用 –xreduction 并行化选项。Oracle Developer Studio 以非确定性的方式优化约简。

  • 避免使用 Fortran 的 DOTMATMUL 运算。Fortran 90 及更高版本中的这些内部函数在不同平台上使用不同方法计算,舍入结果不同。如果还启用了并行化,由于优化约简,结果可能不确定。点积和矩阵乘法操作可以使用可移植 Fortran 编写代码,如 Netlib 软件系统信息库的 LAPACK 库中提供的那样。

5.5.3 无定形计算

在许多语言中,外部表达式求值的顺序并非按语言指定。因此,如果 ranf(x)() 是随机数生成器,在两个 ranf(x)() 调用的求值顺序发生变化时,表达式 ranf(x) * a + ranf(x) * b() 可能会为不同的编译器给出不同结果,或者在相同编译器上给出不同优化级别。

避免使用具有两个外部引用的表达式,将这样的表达式拆分为多个语句,每个语句中最多有一个外部引用。因此

z = ranf(x) * a + ranf(x) * b()

可替换为

t = ranf(x) * a()

z = t + ranf(x) * b()

5.5.4 不可移植类型

C/C++ 中的 long double 实现在 SPARC 和 x86 上的 Studio 中有所不同,前者有效位为 113,后者有效位为 64。因此,使用显式 long double 变量的程序在 SPARC 和 x86 上的执行结果一定会不同。

5.5.5 隐式更高精度

在一些情况下,表达式的求值精度可能高于源代码中显式声明的精度。使用 x87 扩展精度寄存器来计算涉及单精度或双精度变量的表达式时,会出现这种情况。在使用混合乘加运算替换乘法加法对时,也会出现这种情况。

  • 避免将乘加对优化为混合乘加运算。在 –fast 后使用 –fma=none

  • 如果必须使用 –xarch=386 并且没有显式使用长双精度型类型,则在所有变量为浮点数的情况下使用 –fprecision=single 编译,或者在所有变量为双精度的情况下使用 –fprecision=double 编译,可以减少扩展精度表达式求值的影响。但是,如果在 –xarch=386 下使用 Fortran complex*8 变量,则没有方法确保所有表达式求值以单精度进行。最好使用 –m64 而不是 –m32,因为寄存器中传递的函数值精度与函数的精度相同。