5

在进行一个简单的编程练习时,我制作了一个 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 的准确性很差,那么 Fortran 的速度和易于并行化有什么好处?提醒我做事的三种方式:

  1. 正确的方式

  2. 错误的方法

  3. 最大功率方式

“那不是走错路了吗?”

“是啊!但更快!”

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

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

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

4

2 回答 2

15

您的分配意外地将值转换为单精度,然后再转换回双精度。

尝试做你的0.1 *0.1D0 *你应该看到你的问题得到解决。

于 2010-07-26T02:27:18.717 回答
8

正如已经回答的那样,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)Fortran 中的 Long ints

于 2010-07-26T03:39:07.820 回答