【问题标题】:Does Fortran have inherent limitations on numerical accuracy compared to other languages?与其他语言相比,Fortran 在数值准确性方面是否存在固有限制?
【发布时间】:2010-07-26 01:59:58
【问题描述】:

在进行一个简单的编程练习时,我制作了一个 while 循环(Fortran 中的 DO 循环),该循环旨在在实变量达到精确值时退出。

我注意到由于使用了精度,从未满足相等性并且循环变得无限。当然,这并非闻所未闻,建议不要比较两个数字是否相等,最好查看两个数字之间的绝对差是否小于设定的阈值。

令我失望的是我必须将这个阈值设置得这么低,即使变量是双精度的,我的循环才能正确退出。此外,当我在 Perl 中重写此循环的“蒸馏”版本时,我的数值准确性没有问题,并且循环正常退出。

由于产生问题的代码非常小,在 Perl 和 Fortran 中,我想在这里重现它,以防我掩盖了一个重要的细节:

Fortran 代码

PROGRAM precision_test
IMPLICIT NONE

! Data Dictionary
INTEGER :: count = 0 ! Number of times the loop has iterated
REAL(KIND=8) :: velocity
REAL(KIND=8), PARAMETER :: MACH_2_METERS_PER_SEC = 340.0

velocity = 0.5 * MACH_2_METERS_PER_SEC ! Initial Velocity
DO
        WRITE (*, 300) velocity
        300 FORMAT (F20.8)
        IF (count == 50) EXIT
        IF (velocity == 5.0 * MACH_2_METERS_PER_SEC) EXIT
!       IF (abs(velocity - (5.0 * MACH_2_METERS_PER_SEC)) < 1E-4) EXIT
        velocity = velocity + 0.1 * MACH_2_METERS_PER_SEC
        count = count + 1 
END DO

END PROGRAM precision_test

Perl 代码

#! /usr/bin/perl -w
use strict;

my $mach_2_meters_per_sec = 340.0;

my $velocity = 0.5 * $mach_2_meters_per_sec;

while (1) {
        printf "%20.8f\n", $velocity;   
        exit if ($velocity == 5.0 * $mach_2_meters_per_sec);
        $velocity = $velocity + 0.1 * $mach_2_meters_per_sec;
}

Fortran 中注释掉的行是我需要用于循环正常退出的行。请注意,阈值设置为1E-4,我觉得这很可悲。

变量的名称来自我正在执行的基于自学的编程练习,没有任何相关性。

意图是当速度变量达到1700时循环停止。

以下是截断的输出:

Perl 输出

    170.00000000
    204.00000000
    238.00000000
    272.00000000
    306.00000000
    340.00000000

...

   1564.00000000
   1598.00000000
   1632.00000000
   1666.00000000
   1700.00000000

Fortran 输出

    170.00000000
    204.00000051
    238.00000101
    272.00000152
    306.00000203
    340.00000253

...

   1564.00002077
   1598.00002128
   1632.00002179
   1666.00002229
   1700.00002280

如果 Fortran 的准确性很差,它的速度和易于并行化有什么好处?提醒我做事的三种方式:

  1. 正确的道路

  2. 错误的方式

  3. 最大动力之道

“这不就是错误的方式吗?”

“是的!但更快!”

别开玩笑了,我一定是做错了什么。

与其他语言相比,Fortran 在数值准确性方面是否存在固有限制,还是我(很可能)是错误的?

我的编译器是 gfortran(gcc 版本 4.1.2),Perl v5.12.1,在双核 AMD Opteron @ 1 GHZ 上。

【问题讨论】:

  • #3 不应该是“最大功率方式”吗?
  • 哎呀。你说得对。谢谢!
  • Fortran 在这里使用双精度,我怀疑 Perl 也会这样做。但是用浮点数检查相等是在问问题(除非你有一个像0.0144 这样的“安全”数字)。所以我想说这可能是你的测试方法是错误的。
  • 这是一个非常有趣的问题。我运行了您的 Fortran 代码并得到了相同的结果。我的第一个直觉是怀疑 FORTRAN 中的 base-2 转换错误,但如果是这样,为什么 Perl 不受它的影响? FORTRAN 和 Perl 都实现了 IEEE-754。此外,双精度数只应受到 15-16 位的舍入误差,但在您的 Fortran 打印输出中,舍入误差发生得更早。很好奇!
  • @Gilead:是的,我最初也是这么想的,关于舍入误差在 15-16 位,所以我认为,在最坏的情况下,重复的乘法会使错误显着增加我达到〜1700的时间。直到我打印出整个值序列,从初始值到停止点,我才看到即使在第一次乘法之后,舍入误差也比 E-15 大得多。无论如何,谢谢你们的cmets。

标签: perl fortran floating-accuracy double-precision


【解决方案1】:

您的作业意外地将值转换为单精度,然后又转换回双精度。

尝试将您的0.1 * 设为0.1D0 *,您应该会看到问题已解决。

【讨论】:

  • 其实他用的是REAL(kind=8),也就是DOUBLE PRECISION。
  • @Gilead:是的,我最初错过了;我的 Fortran 经验与 GNU Fortran 无关。删除错误答案并输入正确答案。
  • 啊!这就是答案。很微妙!为此 +1。
  • @CmdrGuard:是的,但是最接近 0.1 的单精度值比最接近 0.1 的双精度值更不准确。例如,下面的 0.1 被提升为 double,但这并不能恢复丢失的精度:WRITE (*, "(F20.18)") (0.1-0.1D0)
  • 顺序是关键:十进制常量“0.1”使用单精度转换为二进制表示。与转换为双精度二进制相比,转换为单精度二进制会涉及更多的基本转换问题,如果在我的示例中将常量写入“0.1D0”或“0.1_DR_K”,则会完成。然后,当在具有双精度变量的表达式中使用该单精度二进制值时,它将转换为双精度,并且整个计算以双精度完成。但是基础转换已经完成了!
【解决方案2】:

正如已经回答的那样,Fortran 中的“普通”浮点常量将默认为默认的实数类型,这可能是单精度的。这几乎是一个典型的错误。

另外,使用“kind=8”是不可移植的——它会给你使用 gfortran 的双精度,但不能使用其他一些编译器。在 Fortran >= 90 中为变量和常量指定精度的安全、可移植的方法是使用内部函数,并请求您需要的精度。然后在精度很重要的常量上指定“种类”。一种方便的方法是定义您自己的符号。例如:

integer, parameter :: DR_K = selected_real_kind (14)

REAL(DR_K), PARAMETER :: MACH_2_METERS_PER_SEC = 340.0_DR_K

real (DR_K) :: mass, velocity, energy

energy = 0.5_DR_K * mass * velocity**2

这对于整数也很重要,例如,如果需要较大的值。有关整数的相关问题,请参阅Fortran: integer*4 vs integer(4) vs integer(kind=4)Long ints in Fortran

【讨论】:

  • real(kind=8) 是完全标准的 Fortran 90。所有符合 Fortran 90 的编译器都应该并且确实支持它。也就是说,在 Fortran 90 中要求给定精度的正确方法如下所示:integer, parameter :: dp = selected_real_kind(15, 307) real(kind=dp) :: a 请参阅Fortran Wiki
  • @fB:@MSB 断言 kind=8 不可移植是正确的,并且您说它是标准 Fortran 90 是正确的。该语句的不可移植性出现是因为 while kind= 8 是一个有效的种类选择器,8 的解释取决于实现。当然,大多数当前的编译器(我相信)会将其解释为 8 字节整数,但标准不保证这一点。当然,并非所有 Fortran 编译器都会以相同的方式解释这一点,普通 Fortran 程序员关心的问题之一是跨时间的可移植性。
  • 是的,我意识到我确实使用了一种不太理想的方法来声明精度。我这样做更多是为了简单。也许我应该更改它以使我的示例代码可移植给 Stack Overflow 的读者,但由于这个问题可能会影响其他 Fortran 初学者,所以最好将代码保留为当前形式。
  • @High Performance Mark - 正确。标准委员会对此进行了一些讨论,得出的一般结论是整数类型的数字通常具有误导性,如图所示。 For (i.e. kind=8) - 8 没有特别的意义;它只是一个描述实现精度的数字。它可能很容易是 32 或 112 - 只是一个标签。重要的部分是验证处理器是否支持您所需的精度(cpu + 编译器)并将其与 sel...real_kind 和具有该类型的变量声明的组合使用。
猜你喜欢
  • 1970-01-01
  • 2012-05-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-18
  • 1970-01-01
  • 1970-01-01
  • 2015-01-30
相关资源
最近更新 更多