Precisie en nauwkeurigheid in berekeningen met drijvende komma

Origineel KB-nummer: 125056

Samenvatting

Er zijn veel situaties waarin precisie, afronding en nauwkeurigheid in berekeningen met drijvende komma kunnen werken om resultaten te genereren die verrassend zijn voor de programmeur. Ze moeten de vier algemene regels volgen:

  1. In een berekening met zowel enkele als dubbele precisie is het resultaat meestal niet nauwkeuriger dan één precisie. Als dubbele precisie vereist is, moet u er zeker van zijn dat alle termen in de berekening, inclusief constanten, met dubbele precisie zijn opgegeven.

  2. Ga er nooit vanuit dat een eenvoudige numerieke waarde nauwkeurig wordt weergegeven in de computer. De meeste drijvende kommawaarden kunnen niet precies worden weergegeven als een eindige binaire waarde. Is .0001100110011... bijvoorbeeld .1 in binair (het wordt voor altijd herhaald), zodat het niet met volledige nauwkeurigheid kan worden weergegeven op een computer met behulp van binaire rekenkunde, die alle pc's omvat.

  3. Ga er nooit van uit dat het resultaat nauwkeurig is tot het laatste decimaalteken. Er zijn altijd kleine verschillen tussen het 'waar'-antwoord en wat kan worden berekend met de eindige precisie van een drijvende-kommaverwerkingseenheid.

  4. Vergelijk nooit twee drijvende-kommawaarden om te zien of ze gelijk of niet gelijk zijn. Dit is een gevolg van regel 3. Er zijn bijna altijd kleine verschillen tussen getallen die 'gelijk' moeten zijn. Controleer in plaats daarvan altijd of de getallen bijna gelijk zijn. Met andere woorden, controleer of het verschil ertussen klein of onbeduidend is.

Meer informatie

Over het algemeen zijn de hierboven beschreven regels van toepassing op alle talen, inclusief C, C++ en assembler. In de onderstaande voorbeelden ziet u enkele van de regels met behulp van FORTRAN PowerStation. Alle voorbeelden zijn gecompileerd met BEHULP van FORTRAN PowerStation 32 zonder opties, met uitzondering van de laatste, die is geschreven in C.

Voorbeeld 1

In het eerste voorbeeld ziet u twee dingen:

  • Dat FORTRAN-constanten standaard één precisie zijn (C-constanten zijn standaard dubbele precisie).
  • Berekeningen die enkele precisietermen bevatten, zijn niet veel nauwkeuriger dan berekeningen waarin alle termen één precisie hebben.

Nadat y is geïnitialiseerd met 1,1 (één precisieconstante), is y net zo onnauwkeurig als één precisievariabele.

x = 1.100000000000000  y = 1.100000023841858

Het resultaat van het vermenigvuldigen van één precisiewaarde met een nauwkeurige dubbele precisiewaarde is bijna net zo slecht als het vermenigvuldigen van twee enkele precisiewaarden. Beide berekeningen hebben duizenden keren zoveel fouten als het vermenigvuldigen van twee dubbele precisiewaarden.

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)

Voorbeeldcode

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

Voorbeeld 2

In voorbeeld 2 wordt de kwadratische vergelijking gebruikt. Het toont aan dat zelfs berekeningen met dubbele precisie niet perfect zijn en dat het resultaat van een berekening moet worden getest voordat deze afhankelijk is van of kleine fouten drastische resultaten kunnen hebben. De invoer voor de vierkantswortelfunctie in voorbeeld 2 is slechts licht negatief, maar is nog steeds ongeldig. Als de berekeningen met dubbele precisie geen kleine fouten bevatten, is het resultaat:

Root =   -1.1500000000

In plaats daarvan wordt de volgende fout gegenereerd:

runtime-fout M6201: MATH

  • sqrt: DOMEINfout

Voorbeeldcode

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

Voorbeeld 3

Voorbeeld 3 laat zien dat vanwege optimalisaties die optreden, zelfs als optimalisatie niet is ingeschakeld, waarden tijdelijk een hogere precisie kunnen behouden dan verwacht en dat het niet verstandig is om twee waarden met drijvende komma op gelijkheid te testen.

In dit voorbeeld zijn twee waarden gelijk en niet gelijk. Bij de eerste IF bevindt de waarde van Z zich nog steeds op de stack van de coprocessor en heeft deze dezelfde precisie als Y. Daarom is X niet gelijk aan Y en wordt het eerste bericht afgedrukt. Op het moment van de tweede IF moest Z uit het geheugen worden geladen en had daarom dezelfde precisie en waarde als X, en het tweede bericht wordt ook afgedrukt.

Voorbeeldcode

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

Voorbeeld 4

Het eerste deel van voorbeeldcode 4 berekent het kleinst mogelijke verschil tussen twee getallen dicht bij 1,0. Dit doet u door één bit toe te voegen aan de binaire weergave van 1.0.

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

Sommige versies van FORTRAN ronden de getallen af wanneer ze worden weergegeven, zodat de inherente numerieke onnauwkeurigheid niet zo duidelijk is. Daarom zien x en y er hetzelfde uit wanneer ze worden weergegeven.

Het tweede deel van voorbeeldcode 4 berekent het kleinst mogelijke verschil tussen twee getallen dicht bij 10,0. Dit doet u opnieuw door één bit toe te voegen aan de binaire weergave van 10.0. U ziet dat het verschil tussen getallen bij 10 groter is dan het verschil bij 1. Dit toont het algemene principe aan dat hoe groter de absolute waarde van een getal, hoe minder nauwkeurig het kan worden opgeslagen in een bepaald aantal bits.

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

De binaire weergave van deze getallen wordt ook weergegeven om aan te geven dat ze slechts 1 bit verschillen.

x = 4024000000000001 Hex
y = 4024000000000000 Hex

Het laatste deel van voorbeeldcode 4 laat zien dat eenvoudige niet-herhalende decimale waarden vaak alleen in binair kunnen worden weergegeven door een herhalende breuk. In dit geval x=1,05, waarvoor een herhalende factor CCCCCCCC vereist is.... (Hex) in de mantissa. In FORTRAN wordt het laatste cijfer 'C' naar boven afgerond op 'D' om de hoogst mogelijke nauwkeurigheid te behouden:

x = 3FF0CCCCCCCCCCCD (Hex representation of 1.05D0)

Zelfs na afronding is het resultaat niet perfect nauwkeurig. Er is een fout opgetreden na het minst significante cijfer, dat we kunnen zien door het eerste cijfer te verwijderen.

x-1 = .05000000000000004

Voorbeeldcode

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

Voorbeeld 5

In C zijn zwevende constanten standaard dubbels. Gebruik een 'f' om een floatwaarde aan te geven, zoals 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
   }