浮点计算的精度和准确性

原始 KB 编号: 125056

摘要

在许多情况下,浮点计算中的精度、舍入和准确性可以产生程序员出人意料的结果。 它们应遵循四条一般规则:

  1. 在涉及单精度和双精度的计算中,结果通常不会比单精度更准确。 如果需要双精度,请确保计算中的所有字词(包括常量)都以双精度指定。

  2. 切勿假设计算机中准确表示了简单的数值。 大多数浮点值不能精确表示为有限二进制值。 例如, .1.0001100110011... 二进制 (它永远重复) ,因此无法使用二进制算术(包括所有电脑)在计算机上完全准确地表示它。

  3. 切勿假定结果准确到最后一个小数位数。 “true”答案与可以使用任何浮点处理单元的有限精度计算的内容之间始终存在细微差异。

  4. 切勿比较两个浮点值,以确定它们是否相等。 这是规则 3 的必然结果。 “应该”相等的数字之间几乎总是有细微的差异。 相反,请始终检查,以查看数字是否几乎相等。 换句话说,检查查看它们之间的差异是很小还是微不足道。

更多信息

一般情况下,上述规则适用于所有语言,包括 C、C++和汇编程序。 下面的示例演示了一些使用 FORTRAN PowerStation 的规则。 所有示例都是使用 FORTRAN PowerStation 32 编译的,没有任何选项,但最后一个示例是用 C 编写的。

示例 1

第一个示例演示了两项操作:

  • 默认情况下,FORTRAN 常量是单精度 (C 常量默认为双精度) 。
  • 包含任何单精度术语的计算并不比所有字词都是单精度的计算更准确。

使用 1.1 (单精度常量) 初始化后,y 与单精度变量一样不准确。

x = 1.100000000000000  y = 1.100000023841858

将单个精度值乘以准确的双精度值的结果几乎与将两个单精度值相乘一样糟糕。 这两个计算的误差是将两个双精度值相乘的几千倍。

true = 1.320000000000000 (multiplying 2 double precision values)
y    = 1.320000052452087 (multiplying a double and a single)
z    = 1.320000081062318 (multiplying 2 single precision values)

示例代码

C Compile options: none

       real*8 x,y,z
       x = 1.1D0
       y = 1.1
       print *, 'x =',x, 'y =', y
       y = 1.2 * x
       z = 1.2 * 1.1
       print *, x, y, z
       end

示例 2

示例 2 使用二次公式。 它表明,即使双精度计算也不完美,并且应该先测试计算结果,然后才依赖它,如果小误差会产生剧烈的结果。 示例 2 中平方根函数的输入仅略为负,但仍无效。 如果双精度计算没有轻微错误,则结果将是:

Root =   -1.1500000000

相反,它会生成以下错误:

运行时错误 M6201:MATH

  • sqrt:域错误

示例代码

C Compile options: none

       real*8 a,b,c,x,y
       a=1.0D0
       b=2.3D0
       c=1.322D0
       x = b**2
       y = 4*a*c
       print *,x,y,x-y
       print "(' Root =',F16.10)",(-b+dsqrt(x-y))/(2*a)
       end

示例 3

示例 3 表明,由于优化即使未启用,也会发生优化,因此值可能暂时保留比预期更高的精度,并且测试两个浮点值是否相等是不明智的。

在此示例中,两个值相等和不相等。 在第一个 IF 时,Z 的值仍位于协处理器的堆栈上,其精度与 Y 相同。因此,X 不等于 Y,并且打印出第一条消息。第二次 IF 时,Z 必须从内存中加载,因此具有与 X 相同的精度和值,并且还会打印第二条消息。

示例代码

C Compile options: none

       real*8 y
       y=27.1024D0
       x=27.1024
       z=y
       if (x.ne.z) then
         print *,'X does not equal Z'
       end if
       if (x.eq.z) then
         print *,'X equals Z'
       end if
       end

示例 4

示例代码 4 的第一部分计算接近 1.0 的两个数字之间的最小可能差。 它通过将单个位添加到 1.0 的二进制表示形式来执行此操作。

x   = 1.00000000000000000  (one bit more than 1.0)
y   = 1.00000000000000000  (exactly 1.0)
x-y =  .00000000000000022  (smallest possible difference)

某些版本的 FORTRAN 在显示数字时对其进行舍入,因此固有的数值不明确性就不那么明显了。 这就是为什么 x 和 y 在显示时看起来相同的原因。

示例代码 4 的第二部分计算接近 10.0 的两个数字之间的最小可能差。 同样,它通过将单个位添加到 10.0 的二进制表示形式来执行此操作。 请注意,接近 10 的数字之间的差大于接近 1 的差值。 这演示了一般原则,即数字的绝对值越大,存储在给定位数中的精度就越低。

x   = 10.00000000000000000  (one bit more than 10.0)
y   = 10.00000000000000000  (exactly 10.0)
x-y =   .00000000000000178

还会显示这些数字的二进制表示形式,以显示它们仅相差 1 位。

x = 4024000000000001 Hex
y = 4024000000000000 Hex

示例代码 4 的最后一部分显示,简单的非重复十进制值通常只能用重复分数以二进制形式表示。 在本例中,x=1.05,需要重复因子 CCCCCC.... (mantissa 中的十六进制) 。 在 FORTRAN 中,最后一位数字“C”向上舍入为“D”,以保持尽可能高的准确度:

x = 3FF0CCCCCCCCCCCD (Hex representation of 1.05D0)

即使舍入后,结果也不完全准确。 最低有效数字后存在一些错误,我们可以通过删除第一个数字来查看。

x-1 = .05000000000000004

示例代码

C Compile options: none

       IMPLICIT real*8 (A-Z)
       integer*4 i(2)
       real*8 x,y
       equivalence (i(1),x)

       x=1.
       y=x
       i(1)=i(1)+1
       print "(1x,'x  =',F20.17,'  y=',f20.17)", x,y
       print "(1x,'x-y=',F20.17)", x-y
       print *

       x=10.
       y=x
       i(1)=i(1)+1
       print "(1x,'x  =',F20.17,'  y=',f20.17)", x,y
       print "(1x,'x-y=',F20.17)", x-y
       print *
       print "(1x,'x  =',Z16,' Hex  y=',Z16,' Hex')", x,y
       print *

       x=1.05D0
       print "(1x,'x  =',F20.17)", x
       print "(1x,'x  =',Z16,' Hex')", x
       x=x-1
       print "(1x,'x-1=',F20.17)", x
       print *
       end

示例 5

在 C 中,浮点常量默认为双精度值。 使用“f”指示浮点值,如“89.95f”中所示。

/* Compile options needed: none
*/ 

#include <stdio.h>

void main()
   {
      float floatvar;
      double doublevar;

/* Print double constant. */ 
      printf("89.95 = %f\n", 89.95);      // 89.95 = 89.950000

/* Printf float constant */ 
      printf("89.95 = %f\n", 89.95F);     // 89.95 = 89.949997

/*** Use double constant. ***/ 
      floatvar = 89.95;
      doublevar = 89.95;

printf("89.95 = %f\n", floatvar);   // 89.95 = 89.949997
      printf("89.95 = %lf\n", doublevar); // 89.95 = 89.950000

/*** Use float constant. ***/ 
      floatvar = 89.95f;
      doublevar = 89.95f;

printf("89.95 = %f\n", floatvar);   // 89.95 = 89.949997
      printf("89.95 = %lf\n", doublevar); // 89.95 = 89.949997
   }