Genauigkeit und Genauigkeit bei Gleitkommaberechnungen

Ursprüngliche KB-Nummer: 125056

Zusammenfassung

Es gibt viele Situationen, in denen Genauigkeit, Rundung und Genauigkeit bei Gleitkommaberechnungen funktionieren können, um Ergebnisse zu generieren, die für den Programmierer überraschend sind. Sie sollten die vier allgemeinen Regeln befolgen:

  1. Bei einer Berechnung mit einfacher und doppelter Genauigkeit ist das Ergebnis in der Regel nicht genauer als eine einzelne Genauigkeit. Wenn doppelte Genauigkeit erforderlich ist, stellen Sie sicher, dass alle Begriffe in der Berechnung, einschließlich Konstanten, in doppelter Genauigkeit angegeben werden.

  2. Gehen Sie niemals davon aus, dass ein einfacher numerischer Wert auf dem Computer genau dargestellt wird. Die meisten Gleitkommawerte können nicht genau als endlicher Binärwert dargestellt werden. Beispielsweise .1 ist .0001100110011... in binär (es wiederholt sich für immer), sodass es nicht mit vollständiger Genauigkeit auf einem Computer mit binärer Arithmetik dargestellt werden kann, die alle PCs einschließt.

  3. Gehen Sie niemals davon aus, dass das Ergebnis bis zur letzten Dezimalstelle genau ist. Es gibt immer kleine Unterschiede zwischen der "true"-Antwort und dem, was mit der endlichen Genauigkeit einer beliebigen Gleitkommaverarbeitungseinheit berechnet werden kann.

  4. Vergleichen Sie niemals zwei Gleitkommawerte, um festzustellen, ob sie gleich oder ungleich sind. Dies ist eine Folge von Regel 3. Es gibt fast immer kleine Unterschiede zwischen Zahlen, die "gleich" sein sollten. Überprüfen Sie stattdessen immer, ob die Zahlen nahezu gleich sind. Mit anderen Worten, überprüfen Sie, ob der Unterschied zwischen ihnen klein oder unbedeutend ist.

Weitere Informationen

Im Allgemeinen gelten die oben beschriebenen Regeln für alle Sprachen, einschließlich C, C++ und Assembler. Die folgenden Beispiele veranschaulichen einige der Regeln, die FORTRAN PowerStation verwenden. Alle Beispiele wurden mit FORTRAN PowerStation 32 ohne Optionen kompiliert, mit Ausnahme der letzten, die in C geschrieben ist.

Beispiel 1

Im ersten Beispiel werden zwei Dinge veranschaulicht:

  • Diese FORTRAN-Konstanten haben standardmäßig eine einzelne Genauigkeit (C-Konstanten haben standardmäßig doppelte Genauigkeit).
  • Berechnungen, die einzelne Genauigkeitsbegriffe enthalten, sind nicht viel genauer als Berechnungen, bei denen alle Begriffe eine einzelne Genauigkeit aufweisen.

Nach der Initialisierung mit 1,1 (einer einzelnen Genauigkeitskonstante) ist y genauso ungenau wie eine einzelne Genauigkeitsvariable.

x = 1.100000000000000  y = 1.100000023841858

Das Ergebnis der Multiplikation eines einzelnen Genauigkeitswerts mit einem genauen Wert mit doppelter Genauigkeit ist fast so schlecht wie das Multiplizieren von zwei Werten mit einfacher Genauigkeit. Beide Berechnungen weisen tausendmal so viele Fehler auf wie das Multiplizieren von zwei Werten mit doppelter Genauigkeit.

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)

Beispielcode

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

Beispiel 2

Beispiel 2 verwendet die quadratische Gleichung. Es zeigt, dass selbst Berechnungen mit doppelter Genauigkeit nicht perfekt sind und dass das Ergebnis einer Berechnung getestet werden sollte, bevor es davon abhängig ist, ob kleine Fehler zu drastischen Ergebnissen führen können. Die Eingabe für die Quadratwurzelfunktion in Stichprobe 2 ist nur geringfügig negativ, aber immer noch ungültig. Wenn die Berechnungen mit doppelter Genauigkeit keine geringfügigen Fehler aufweisen, würde das Ergebnis wie folgt sein:

Root =   -1.1500000000

Stattdessen wird der folgende Fehler generiert:

Laufzeitfehler M6201: MATH

  • sqrt: DOMAIN-Fehler

Beispielcode

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

Beispiel 3

Beispiel 3 zeigt, dass Werte aufgrund von Optimierungen, die auch dann auftreten, wenn die Optimierung nicht aktiviert ist, vorübergehend eine höhere Genauigkeit beibehalten können als erwartet, und dass es unklug ist, zwei Gleitkommawerte auf Gleichheit zu testen.

In diesem Beispiel sind zwei Werte gleich und ungleich. Beim ersten WENN befindet sich der Wert von Z noch im Stapel des Coprozessors und hat die gleiche Genauigkeit wie Y. Daher ist X nicht gleich Y, und die erste Nachricht wird ausgegeben. Zum Zeitpunkt des zweiten WENN musste Z aus dem Speicher geladen werden und hatte daher die gleiche Genauigkeit und den gleichen Wert wie X, und die zweite Nachricht wird ebenfalls ausgegeben.

Beispielcode

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

Beispiel 4

Im ersten Teil von Beispielcode 4 wird die kleinste mögliche Differenz zwischen zwei Zahlen in der Nähe von 1,0 berechnet. Dazu wird der binären Darstellung von 1.0 ein einzelnes Bit hinzugefügt.

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

Einige Versionen von FORTRAN runden die Zahlen bei der Anzeige so ab, dass die inhärente numerische Ungenauigkeit nicht so offensichtlich ist. Aus diesem Grund sehen x und y bei der Anzeige gleich aus.

Im zweiten Teil von Beispielcode 4 wird der kleinste mögliche Unterschied zwischen zwei Zahlen in der Nähe von 10,0 berechnet. Auch dies geschieht, indem der binären Darstellung von 10.0 ein einzelnes Bit hinzugefügt wird. Beachten Sie, dass die Differenz zwischen Zahlen in der Nähe von 10 größer ist als die Differenz in der Nähe von 1. Dies zeigt das allgemeine Prinzip: Je größer der absolute Wert einer Zahl, desto weniger genau kann sie in einer bestimmten Anzahl von Bits gespeichert werden.

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

Die binäre Darstellung dieser Zahlen wird auch angezeigt, um zu zeigen, dass sie sich nur um ein Bit unterscheiden.

x = 4024000000000001 Hex
y = 4024000000000000 Hex

Der letzte Teil des Beispielcodes 4 zeigt, dass einfache, nicht wiederholte Dezimalwerte häufig nur durch einen wiederholten Bruchteil in binär dargestellt werden können. In diesem Fall x=1,05, was einen wiederholungsfaktor CCCCCCCC erfordert.... (Hex) in der Mantisse. In FORTRAN wird die letzte Ziffer "C" auf "D" aufgerundet, um die höchstmögliche Genauigkeit beizubehalten:

x = 3FF0CCCCCCCCCCCD (Hex representation of 1.05D0)

Auch nach dem Runden ist das Ergebnis nicht ganz genau. Nach der am wenigsten signifikanten Ziffer tritt ein Fehler auf, der durch Entfernen der ersten Ziffer angezeigt wird.

x-1 = .05000000000000004

Beispielcode

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

Beispiel 5

In C sind unverankerte Konstanten standardmäßig Doubles. Verwenden Sie ein "f", um einen Gleitkommawert anzugeben, wie in "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
   }