【发布时间】: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 的准确性很差,它的速度和易于并行化有什么好处?提醒我做事的三种方式:
正确的道路
错误的方式
最大动力之道
“这不就是错误的方式吗?”
“是的!但更快!”
别开玩笑了,我一定是做错了什么。
与其他语言相比,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