Zwevendekommagetal

Uit Wikipedia, de vrije encyclopedie
Ga naar: navigatie, zoeken
Nuvola single chevron right.svg Dit artikel gaat over een gegevenstype, zie Wetenschappelijke notatie en Reëel getal voor andere betekenissen

Een zwevendekommagetal of drijvendekommagetal (Engels: floating-point number) is een gegevenstype dat een vaste geheugenruimte beslaat, en een grote variëteit van rationale getallen kan bevatten, van zeer kleine tot zeer grote. De relatieve nauwkeurigheid waarin getallen worden gerepresenteerd is over de hele linie ongeveer gelijk. Het is de digitale versie van de wetenschappelijke notatie.

Computers[bewerken]

Om grotere getallen in dezelfde beperkte geheugenruimte op te kunnen slaan, wordt gebruikgemaakt van de zwevendekommanotatie. Niet ieder getal is representeerbaar, er is vaak een afrondfout.

Het gegevenstype[bewerken]

De zogenaamde type specifier kan, afhankelijk van programmeertaal en digitale incarnatie, bijvoorbeeld real, float, single, extended, double, enzovoorts genoemd worden. Het gegevenstype wordt vaak verward met het wiskundig begrip rationaal getal, hetgeen een belangrijke bron van bugs bij zwevendekommagetallen is. Om berekeningen met zwevendekommagetallen te versnellen is vaak een aparte Floating Point Unit aanwezig. Ook verschillen tussen FPU's kunnen voor bugs zorgen.

Een zwevendekommagetal is een digitale representatie van een rationaal getal. In iedere implementatie zijn er maar eindig veel zwevendekommagetallen. Ze worden gebruikt in computers om reële getallen, zij het benaderend, voor te stellen. Een zwevendekommagetal wordt voorgesteld als een geheel getal of vastekommagetal (de mantisse), vermenigvuldigd met een grondtal (meestal 2 of 10) verheven tot een bepaalde macht (de exponent). Deze manier van voorstellen is het digitale analogon van de wetenschappelijke notatie.

Voor het zwevendekommagetal x, bepaald door de mantisse m en de exponent e bij het grondtal r, geldt:

 x = m \cdot r^e

De drie grootheden m, r en e hebben elk hun eigen bereik, afhankelijk van de gekozen digitale incarnatie.

Een zwevendekommaberekening is een rekenkundige bewerking met zwevendekommagetallen. In het gunstigste geval is het resultaat van een bewerking de werkelijke uitkomst, afgerond op het dichtstbijzijnde representeerbare getal. Het verschil is de afrondfout. Bij een keten van berekeningen kunnen dergelijke fouten cumuleren, waardoor niet alle cijfers in het resultaat significant zijn.

Eigenschappen van zwevendekommaberekeningen[bewerken]

Zwevendekommabewerkingen zijn niet helemaal associatief: vanwege de eindige nauwkeurigheid kan bij bewerkingen met zwevendekommabewerkingen de volgorde waarin de bewerkingen gebeuren, verschil maken voor de uitkomst. In het algemeen geldt voor zwevendekommagetallen x, y en z:

  •  (x + y) + z \approx x + (y + z)
  •  (x \cdot y) \cdot z \approx x \cdot (y \cdot z)

Hetzelfde geldt voor de distributieve eigenschap; in het algemeen geldt:

  •  x \cdot (y + z) \approx (x \cdot y) + (x \cdot z)

Soms is het verschil groot en is een bepaalde volgorde (of meer algemeen een bepaald algoritme) duidelijk beter dan een andere, ook al zijn ze wiskundig gelijkwaardig, in de zin dat bij exact rekenen de uitkomst hetzelfde zou zijn. De numerieke analyse houdt zich hier mee bezig.

Zwevendekommagetallen als foutbron[bewerken]

Om deze reden is het niet aan te raden twee zwevendekommagetallen direct met elkaar te vergelijken, zoals in het volgende voorbeeld, dat in C is geschreven:

#include <math.h>
#include <stdio.h>
 
int main(void)
{
  float foo = 2 * M_PI /  3;
  float bar = 8 * M_PI / 12;
  /* FOUT */
  if (foo == bar)
  {
    printf("ja, hij doet het\n");
  }
  return 0;
}

Nog erger is het in een lus:

#include <math.h>
#include <stdio.h>
 
int main(void)
{
  float foo;
  float bar = 8 * M_PI / 12;
  /* FOUT */
  for (foo = 0.0; foo != bar; foo += M_PI / 10)
  {
    printf("En nog een keer...\n");
  }
  return 0;
}

Omdat de niet-significante bits wel verschillen, maar toch worden meegenomen in de vergelijking, kunnen foo en bar van elkaar verschillen. Dit levert bijna gegarandeerd niet het resultaat waar men op rekent. Erger nog, het kan op verschillende computers verschillend gedrag vertonen.

Een correcte manier om twee zwevendekommagetallen met elkaar te vergelijken is:

#include <math.h>
#include <stdio.h>
 
#define EPSILON  (1E-10)
 
int main(void)
{
  float foo = 2 * M_PI /  3;
  float bar = 8 * M_PI / 12;
 
  if (abs(foo - bar) < EPSILON)
  {
    printf("natuurlijk doet hij het...\n");
  }
  return 0;
}

Als de absolute waarde van het verschill tussen foo en bar kleiner is dan een schatting van de maximale fout, zijn ze aan elkaar gelijk. Deze fout, epsilon ('ε'), verschilt per toepassing, aangezien de aard van de toepassing en de berekeningen die deze met zich meebrengt, de cumulatieve fout bepaalt.

Een andere bron voor vreemde resultaten is het aftrekken van twee waarden die bijna aan elkaar gelijk zijn. Aangezien de meer significante cijfers aan elkaar gelijk zijn, blijft in het resultaat een veel geringer aantal, of zelfs helemaal geen, significante cijfers over. Men spreekt in dit geval van een "gedegenereerd getal".

#include <stdio.h>
 
int main(void)
{
  float foo = 1.000001E-12;
  float bar = 1.000002E-12;
 
  printf("%g\n", (bar - foo));
  return 0;
}

Levert onverwachte resultaten:

$ ./try_float
9.75782e-19

Dus niet 10−18, zoals eigenlijk zou moeten. De oorzaak is dat 1.000001E-12 en 1.000002E-12 intern niet representeerbaar zijn en daarom worden afgerond, en dat vervolgens deze afrondfouten doorwerken in het verschil; de resulterende fout is relatief groot in gevallen waarbij de absolute waarde van het resultaat veel kleiner is dan die van de oorspronkelijke getallen, zoals hier.

Interne codering[bewerken]

In het algemeen[bewerken]

Een zwevendekommagetal a kan worden gerepresenteerd door twee getallen m en e, namelijk als a = m × be. In elk dergelijk systeem wordt een basis (grondtal of radix) b gekozen en een nauwkeurigheid p (het aantal cijfers dat moet worden opgeslagen). m (de significant of mantisse) is een p-cijferig getal van de vorm ±d,ddd...ddd (waarbij elk cijfer d een geheel getal is uit het interval van 0 tot en met b−1). Als het eerste cijfer van m ongelijk is aan 0, wordt het getal genormaliseerd genoemd. Veel systemen gebruiken een aparte tekenbit (s, die −1 of +1 representeert) en vereisen dat m niet-negatief is. Daarbij is e de exponent.

Deze methode maakt het mogelijk dat een groot bereik aan waarden wordt gerepresenteerd binnen een gegeven veldgrootte, wat niet mogelijk is in een vastekommanotatie.

Voorbeeld: een zwevendekommagetal met vier decimale cijfers (b = 10, p = 4) en een exponentbereik van ±4 kan worden gebruikt om 43210, 4,321, of 0,0004321 te representeren, maar heeft niet genoeg nauwkeurigheid om 432,123 en 43212,3 te representeren (wat afgerond zou worden tot 432,1 en 43210). Vanzelfsprekend is in de praktijk het aantal cijfers groter dan vier.

Voorts kennen zwevendekommarepresentaties vaak de bijzondere waarden +∞, −∞ (plus en min oneindig) en NaN (Not a Number, geen getal). Oneindigheden worden gebruikt als de resultaten te groot zijn om te worden gerepresenteerd en NaN's geven een ongeoorloofde bewerking aan of een uitkomst die niet is te representeren als een reëel getal.

IEEE 754[bewerken]

Het IEEE heeft ooit een standaard gedefinieerd (IEEE 754) voor het representeren van binaire zwevendekommagetallen. Er is een enkeleprecisievariant (single) van 32 bits (1 tekenbit, 8 exponentbits en 23 mantissebits) en een dubbeleprecisievariant (double) van 64 bits (1 + 11 + 52). Een reëel getal wordt gerepresenteerd als s × m × 2e. We leggen hier uit hoe de enkele-precisievariant in detail werkt. De strekking van de dubbele precisie is hetzelfde.

Teken, exponent en mantisse[bewerken]

Het tekenbit is 1 voor een negatief getal en 0 voor een positief getal.

De exponent van 8 bits is gecodeerd met een offset van 127. Normaal representeren de 8 bits de getallen 0 (binair 00000000) tot en met 255 (11111111). Daar moet nu 127 vanaf getrokken worden. Dat wil zeggen dat nu de getallen –127 tot en met 128 weergegeven kunnen worden. Dus het getal 127 (01111111) betekent nu 0.

In een genormaliseerd binair getal is de eerste bit altijd 1, dus dit hoeft niet daadwerkelijk opgeslagen te worden. Dit cijfer wordt ook de 'hidden bit' genoemd.

Het complete getal[bewerken]

We hebben nu een genormaliseerd getal in de vorm:

sxxxxxxxxmmmmmmmmmmmmmmmmmmmmmmm

waarbij:

teken = 1 – 210 * s dus als s = 1 dan teken = –1; als s = 0 dan teken = 1
exponent = xxxxxxxxxx2 – 12710
mantisse = 1,mmmmmmmmmmmmmmmmmmmmmmm2; de 1 voor de komma is de verborgen bit.

De lagergeschreven getallen 2 en 10 geven aan in welk talstelsel de waarden zijn genoteerd.

Het reële getal heeft de waarde:

teken * mantisse * 210exponent.

Speciale waarden[bewerken]

Nu is het op deze manier niet mogelijk om het getal 0 te coderen. Om dit en nog wat andere speciale waarden mogelijk te maken is er een aantal bitpatronen waarover een afspraak is.

Genormaliseerde getallen 0 < xxxxxxxx < 25510; mantisse > 0; zoals hierboven beschreven
Gedenormaliseerde getallen   xxxxxxxx = 0; mantisse > 0; waarden die te klein zijn voor normalisatie
De waarde 0 xxxxxxxx = 0; mantisse = 0; −0, +0, en waarden die te klein zijn voor representatie
Oneindig xxxxxxxx = 25510; mantisse = 0; waarden die te groot zijn voor representatie
Not a number (NaN) xxxxxxxx = 25510; mantisse > 0; vele NaN's zijn mogelijk

Deze standaard wordt in bijna alle computers gebruikt. De 'NaN' wordt gebruikt om het resultaat van een onmogelijke berekening weer te geven, zoals de vierkantswortel van een negatief getal. De waarde oneindig wordt gebruikt om resultaten weer te geven van berekeningen waarvan het resultaat te groot is om weer te geven, zoals een deling door een heel klein getal of nul.

Alle speciale waarden hebben een positieve en een negatieve variant, aangezien de tekenbit op zichzelf staat.

Noten[bewerken]

Bronnen[bewerken]