Přesnost a přesnost při výpočtech s plovoucí desetinou čárkou

Původní číslo KB: 125056

Souhrn

V mnoha situacích může přesnost, zaokrouhlování a přesnost ve výpočtech s plovoucí desetinou čárkou generovat výsledky, které programátora překvapí. Měly by se řídit čtyřmi obecnými pravidly:

  1. Při výpočtu zahrnujícím jednoduchou i dvojitou přesnost nebude výsledek obvykle o nic přesnější než jednoduchá přesnost. Pokud je vyžadována dvojitá přesnost, ujistěte se, že všechny výrazy ve výpočtu, včetně konstant, jsou zadány s dvojitou přesností.

  2. Nikdy nepředpokládejte, že je v počítači přesně reprezentována jednoduchá číselná hodnota. Většina hodnot s plovoucí desetinou čárkou nemůže být přesně reprezentována jako konečná binární hodnota. Je například .1.0001100110011... v binárním formátu (opakuje se navždy), takže ho nelze s úplnou přesností znázornit na počítači pomocí binární aritmetické metody, která zahrnuje všechny počítače.

  3. Nikdy nepředpokládejte, že je výsledek přesný na poslední desetinné místo. Mezi odpovědí "true" a tím, co lze vypočítat s konečnou přesností jakékoli jednotky zpracování s plovoucí desetinnou čárkou, jsou vždy malé rozdíly.

  4. Nikdy nerovnávejte dvě hodnoty s plovoucí desetinou čárkou, abyste zjistili, jestli jsou stejné nebo nerovnající se. Toto je důsledkem pravidla 3. Mezi čísly, která by měla být rovna, budou téměř vždy malé rozdíly. Místo toho vždy zkontrolujte, jestli jsou čísla téměř stejná. Jinými slovy, zkontrolujte, jestli je rozdíl mezi nimi malý nebo nevýznamný.

Další informace

Obecně platí, že výše popsaná pravidla platí pro všechny jazyky, včetně C, C++ a assembleru. Následující ukázky ukazují některá pravidla používající fortRAN PowerStation. Všechny ukázky byly zkompilovány pomocí FORTRAN PowerStation 32 bez jakýchkoliv možností, s výjimkou poslední, která je napsaná v jazyce C.

Ukázka 1

První ukázka ukazuje dvě věci:

  • Konstanty FORTRAN mají ve výchozím nastavení jednoduchou přesnost (konstanty C mají ve výchozím nastavení dvojitou přesnost).
  • Výpočty, které obsahují jednotlivé termíny přesnosti, nejsou o moc přesnější než výpočty, ve kterých mají všechny termíny jednoduchou přesnost.

Po inicializaci pomocí 1,1 (jedné konstanty přesnosti) je y stejně nepřesné jako jedna proměnná přesnosti.

x = 1.100000000000000  y = 1.100000023841858

Výsledek vynásobení jedné hodnoty přesnosti přesnou hodnotou dvojité přesnosti je téměř stejně špatný jako vynásobení dvou jednoduchých hodnot přesnosti. Oba výpočty mají tisíckrát tolik chyb jako vynásobení dvou dvojitých hodnot přesnosti.

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)

Ukázkový kód

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

Ukázka 2

Ukázka 2 používá kvadratickou rovnici. Ukazuje, že ani výpočty s dvojitou přesností nejsou dokonalé a že výsledek výpočtu by měl být testován před tím, než bude záviset na tom, jestli malé chyby mohou mít drastické výsledky. Vstup funkce odmocniny ve vzorku 2 je pouze mírně záporný, ale stále je neplatný. Pokud by výpočty s dvojitou přesností neměly mírné chyby, výsledek by byl:

Root =   -1.1500000000

Místo toho vygeneruje následující chybu:

Chyba běhu M6201: MATEMATIKA

  • sqrt: Chyba DOMÉNY

Ukázkový kód

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

Ukázka 3

Ukázka 3 ukazuje, že kvůli optimalizaci, ke kterým dochází i v případě, že optimalizace není zapnutá, můžou si hodnoty dočasně zachovat vyšší přesnost, než se čekalo, a že není moudré testovat rovnost dvou hodnot s plovoucí desetinou čárkou.

V tomto příkladu jsou dvě hodnoty rovny i nerovné. V první když je hodnota Z stále na zásobníku koprocesoru a má stejnou přesnost jako Y. X se proto nerovná Y a první zpráva se vytiskne. V době druhé funkce KDYŽ muselo být Z načteno z paměti, a proto mělo stejnou přesnost a hodnotu jako X, a druhá zpráva se také vytiskne.

Ukázkový kód

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

Ukázka 4

První část vzorového kódu 4 vypočítá nejmenší možný rozdíl mezi dvěma čísly blížícími se 1,0. Dělá to tak, že přidá jeden bit do binární reprezentace 1.0.

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

Některé verze nástroje FORTRAN zaokrouhlují čísla při jejich zobrazení tak, aby inherentní číselná nepřesná hodnota nebyla tak zřejmá. To je důvod, proč x a y vypadají při zobrazení stejně.

Druhá část vzorového kódu 4 vypočítá nejmenší možný rozdíl mezi dvěma čísly blížícími se hodnotě 10,0. Opět to dělá přidáním jednoho bitu do binární reprezentace 10.0. Všimněte si, že rozdíl mezi čísly blížícími se 10 je větší než rozdíl blízký 1. To demonstruje obecný princip, že čím větší je absolutní hodnota čísla, tím méně přesně může být uloženo v daném počtu bitů.

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

Zobrazí se také binární reprezentace těchto čísel, aby bylo vidět, že se liší pouze o 1 bit.

x = 4024000000000001 Hex
y = 4024000000000000 Hex

Poslední část vzorového kódu 4 ukazuje, že jednoduché neopakující se desetinné hodnoty mohou být často reprezentovány v binární soustavě pouze opakujícím se zlomkem. V tomto případě x=1,05, který vyžaduje opakující se faktor CCCCCCCC.... (Hex) v manissa. V nástroji FORTRAN se poslední číslice "C" zaokrouhlí nahoru na "D", aby byla zachována nejvyšší možná přesnost:

x = 3FF0CCCCCCCCCCCD (Hex representation of 1.05D0)

Ani po zaokrouhlení není výsledek úplně přesný. Za nejméně významnou číslicí je určitá chyba, kterou můžeme vidět odebráním první číslice.

x-1 = .05000000000000004

Ukázkový kód

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

Ukázka 5

V jazyce C jsou plovoucí konstanty ve výchozím nastavení dvojité hodnoty. K označení plovoucí hodnoty použijte "f", například "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
   }