【问题标题】:gfortran loss of precision in mixed integer and real calculationsgfortran 在混合整数和实数计算中的精度损失
【发布时间】:2019-11-14 22:32:45
【问题描述】:

我正在移植一些曾经在 IBM AIX 4.3 机器 (IBM XL Fortran) 上运行的旧 Fortran 代码。我在运行 CentOS 7.7 的 x86_64 机器上使用 gfortran 编译时在遗留代码中遇到了以下计算,该计算产生了错误的答案。

ISCRATCH2 = ISCRATCH2 + (7 * (2.**16)) + TEMP2

ISCRATCH2的初始值为X'0B000000',TEMP2的值为4053。计算结果为X'B070FD0',应该是X'B070FD5'。如果我从 2 中删除点(使其成为整数而不是实数),则该值是正确的。这是一个演示问题的小程序。

      program main
      implicit none

      Integer*2 TEMP1
      Integer*2 TEMP2
      Integer*4 ISCRATCH1
      Integer*4 ISCRATCH2
      Integer*4 ISCRATCH3

      ISCRATCH1 = X'0B000000'
      ISCRATCH2 = X'0B000000'
      TEMP1 = 4053
      TEMP2 = 4053

      ISCRATCH3 = 7 * (2.**16)
      ISCRATCH3 = X'0B000000' + ISCRATCH3 + TEMP2

      ISCRATCH1 = ISCRATCH1 + (7 * (2**16)) + TEMP1

      ISCRATCH2 = ISCRATCH2 + (7 * (2.**16)) + TEMP2

      Print 1102, ISCRATCH1, TEMP1
1102  FORMAT('ISCRATCH1 is ', Z8, ' and TEMP1 is ', Z8)

      Print 1103, ISCRATCH2, TEMP2
1103  FORMAT('ISCRATCH2 is ', Z8, ' and TEMP2 is ', Z8)

      Print 1104, ISCRATCH3, TEMP2
1104  FORMAT('ISCRATCH3 is ', Z8, ' and TEMP2 is ', Z8)

  end program main

当我运行上面的程序时,它会给出以下输出

ISCRATCH1 is  B070FD5 and TEMP1 is      FD5
ISCRATCH2 is  B070FD0 and TEMP2 is      FD5
ISCRATCH3 is  B070FD5 and TEMP2 is      FD5

如果我添加

-ffpe-trap=inexact

对于编译器选项,我在第 20 行得到一个浮点异常。

这是预期的行为吗?如果是这样,如果我将它分成多行(ISCRATCH3 计算),为什么它会得到正确的答案并且不会引发浮点异常?我在 CentOS 7.7 上使用 gfortran 4.8.5-39。这段代码似乎可以与 IBM 编译器一起正常工作。我意识到以这种方式混合数据类型有点愚蠢,只需更改代码即可解决。但是,这种东西在一个非常大的程序的代码中的很多地方都使用了。我可以做些什么来解决这个问题而不必找到每个实例?

【问题讨论】:

  • 代码显然不符合 Fortran,所以编译器可以给你任何答案。十六进制文字常量以Z 开头。十六进制不能出现在表达式中(即第 10、11 和 16 行无效)。
  • 您的问题的答案是:是的;实数在赋值时转换为整数,然后在后面的表达式中添加到其他整数;和不。原因很简单。 32 位有符号整数具有 31 位精度。假设一个 32 位 IEEE-754 浮点实体,带有 2.**16 的表达式具有 24 位精度。
  • 值得检查2.0**16 本身有多少[作为浮点数]。编译器/rtl 可能会使用公式exp(16*ln(2.0)),这可能会给出不准确的答案。
  • @Lorinczy,Fortran 编译器可能会将2.**16 转换为exp(16*ln(2.)),但它不是一个很好的编译器。使用 gfortran,表达式被 4 次乘法常数折叠。

标签: fortran gfortran


【解决方案1】:

经过更多实验后,问题似乎是(除了代码格式不正确的事实)添加了 0x0B000000 并且该数字超出了@steve 建议的 32 位浮点数的精度。我认为原始语句中发生的事情是,因为其中一个值是实数,所以在分配期间将答案转换回整数之前,它会将它们全部转换为实数。

为了进一步演示执行ISCRATCH4 = 184549376 + 10. 的问题会导致 184549392,而ISCRATCH4 = 184549376 + 10 会导致 184549386。有趣的是,ISCRATCH4 = 184549376 + 1. 只返回 184549376。如果 ISCRATCH4 从 Integer*4 更改为 Real*4,则所有表达式都返回正确回答。

我发现如果我将-fdefault-real-8 添加到编译选项中,答案总是正确的并且浮点异常消失。我假设这是因为将 0x0B000000(或 184549376)常数转换为实数现在足够大,可以在不损失精度的情况下保持数字。此修复程序应该适用于我的应用程序。除了增加内存使用量之外,还有什么不受欢迎的原因吗?

【讨论】:

  • 混合模式算法的规则在 Fortran 标准中得到了很好的规定。 2.**16 是默认的真实类型。在表达式求值期间,RHS 中的整数被提升为默认的实数类型。然后,默认的 real kind 值在赋值时转换为INTEGER*4(是的,我知道这个声明是非标准的)。
  • 使用-fdefault-real-8 是个坏主意。如果您要解决问题,请正确执行。 Gfortran 开发者应该删除-fdefault-real-8。它已损坏,应避免使用。修复计算的正确方法是使用内部函数INT 和/或REAL 进行转换。试试ISCRATCH2 = ISCRATCH2 + INT(7 * (2.**16), KIND(1)) + TEMP2。这是因为2.**16 是一个可精确表示的整数浮点值,并且 INT() 将其转换为默认整数类型,因此 RHS 表达式被计算为整数而不是实数。
  • @evets,感谢您的回复。您能否提供更多详细信息,说明为什么 -fdefault-real-8 是一个坏主意,以及使用它可能会遇到哪些其他问题?我意识到更改代码是正确的方法,这对那一行来说不是问题。问题是我和我的团队正在移植一个非常大的应用程序,它有超过 200,000 行 Fortran 代码。我担心的是代码中可能还有许多其他实例,如果不评估每一行代码就不容易识别。有没有更好的方法来识别代码中发生这种行为的位置?
  • -default-real-8 中断存储关联。如果这是旧代码,它可能包含COMMONEQUIVALENCE。存储关联对于这些功能非常重要。请注意,如果您要链接到外部库,您可能需要使用 -fdefault-real-8 选项编译这些库。在移植期间,您可能会考虑使用 -freal-4-real-8 和类似选项,但如果可能,即使这些选项也应避免使用。
  • @evets,感谢您提供额外的详细信息。该代码确实使用了COMMON 块和EQUIVALENCE 语句。我将与我的团队协商,我们将找出最好的前进方式。再次感谢!
猜你喜欢
  • 2015-06-28
  • 1970-01-01
  • 2018-11-26
  • 2021-07-13
  • 2018-02-27
  • 2014-06-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多