U beweert dat er inherente onnauwkeurigheden zijn in drijvende-kommagetallen. Ik denk dat dit het verdient om eerst een beetje te worden onderzocht.
Bij het kiezen van een cijfersysteem om een getal weer te geven (of het nu op een stuk papier, in een computercircuit of elders is), zijn er twee afzonderlijke aandachtspunten:
-
zijn basis; en
-
zijn formaat .
Kies een basis, een willekeurige basis...
Beperkt door eindige ruimte, kan men geen willekeurig lid vertegenwoordigen van een oneindige verzameling
. Bijvoorbeeld:hoeveel papier je ook koopt of hoe klein je handschrift ook is, het is altijd mogelijk om een geheel getal te vinden dat niet in de gegeven ruimte past (je kunt gewoon extra cijfers blijven toevoegen totdat het papier op is). Dus, met gehele getallen , beperken we onze eindige ruimte meestal tot het weergeven van alleen die die binnen een bepaald interval vallen, bijv. als we ruimte hebben voor het positieve/negatieve teken en drie cijfers, kunnen we ons beperken tot het interval [-999,+999]
.
Elke non-empty interval
bevat een oneindige reeks reële getallen. Met andere woorden, ongeacht welk interval men de reële getallen overneemt —zij het [-999,+999]
, [0,1]
, [0.000001,0.000002]
of iets anders - er is nog steeds een oneindige reeks reële getallen binnen dat interval (je hoeft alleen maar (niet-nul) cijfers met breuken toe te voegen)! Daarom moeten willekeurige reële getallen altijd worden "afgerond" tot iets dat kan worden weergegeven in een eindige ruimte.
De verzameling reële getallen die in een eindige ruimte kan worden weergegeven, hangt af van het gebruikte getallenstelsel. In onze (bekende) positioneel
base-10
systeem is eindige ruimte voldoende voor de helft (0.510
) maar niet voor een derde (0.33333…10
); daarentegen in de (minder bekende) positionele base-9
systeem is het andersom (dezelfde nummers zijn respectievelijk 0.44444…9
en 0.39
). Het gevolg van dit alles is dat sommige getallen die kunnen worden weergegeven met slechts een kleine hoeveelheid ruimte in het grondtal 10 (en daarom verschijnen erg "rond" zijn voor ons mensen), b.v. een tiende, zou eigenlijk vereisen dat oneindige binaire circuits nauwkeurig worden opgeslagen (en daarom niet erg "rond" lijken voor onze digitale vrienden)! Met name, aangezien 2 een factor 10 is, is hetzelfde omgekeerd niet waar:elk getal dat kan worden weergegeven met eindig binair getal, kan ook worden weergegeven met eindig decimaal.
We kunnen niet beter doen voor continue hoeveelheden. Uiteindelijk moeten dergelijke hoeveelheden een eindige representatie gebruiken in sommige cijfersysteem:het is willekeurig of dat systeem gemakkelijk is op computercircuits, op menselijke vingers, op iets anders of op helemaal niets - welk systeem ook wordt gebruikt, de waarde moet worden afgerond en daarom is het altijd resulteert in "weergavefout".
Met andere woorden, zelfs als men een perfect nauwkeurig meetinstrument heeft (wat fysiek onmogelijk is), dan zal elke meting die het rapporteert al afgerond zijn naar een getal dat toevallig op het display past (in welke basis het ook gebruikt - meestal decimaal, om voor de hand liggende redenen). Dus "86,2 oz" is eigenlijk nooit "86,2 oz " maar eerder een weergave van "iets tussen 86.1500000... oz en 86.2499999... oz ". (Omdat het instrument in werkelijkheid niet perfect is, kunnen we alleen maar zeggen dat we een of andere mate van vertrouwen dat de werkelijke waarde binnen dat interval valt, maar dat wijkt hier zeker af.
Maar we kunnen het beter doen voor discrete hoeveelheden . Dergelijke waarden zijn geen "willekeurige reële getallen" en daarom is geen van het bovenstaande op hen van toepassing:ze kunnen exact worden weergegeven in het getallenstelsel waarin ze zijn gedefinieerd - en inderdaad, zou moeten zijn (omdat het converteren naar een ander getallenstelsel en het afkappen naar een eindige lengte zou resulteren in afronding op een onnauwkeurig getal). Computers kunnen dergelijke situaties (inefficiënt) afhandelen door het getal als een tekenreeks weer te geven:b.v. overweeg ASCII of BCD codering.
Een opmaak toepassen...
Aangezien het een eigenschap is van de (enigszins willekeurige) basis van het getallenstelsel, of een waarde al dan niet "rond" lijkt te zijn, heeft geen invloed op de precisie . Dat is een heel belangrijke observatie , wat indruist tegen de intuïtie van veel mensen (en dat is de reden dat ik zoveel tijd heb besteed aan het uitleggen van de numerieke basis hierboven).
Precisie wordt in plaats daarvan bepaald door hoeveel significante cijfers
een vertegenwoordiging heeft . We hebben een opslagformaat nodig dat onze waarden kan vastleggen op minstens zoveel significante cijfers als we ze als correct beschouwen . Als voorbeeld nemen we waarden die we als correct beschouwen wanneer ze worden vermeld als 86.2
en 0.0000862
, de twee meest voorkomende opties zijn:
-
Vast punt , waarbij het aantal significante cijfers afhankelijk is van de grootte :bijv. in een vaste weergave van 5 decimalen zouden onze waarden worden opgeslagen als
86.20000
en0.00009
(en hebben daarom respectievelijk 7 en 1 significante nauwkeurigheidscijfers). In dit voorbeeld is precisie verloren in de laatste waarde (en inderdaad, het zou niet veel meer kosten voor ons om helemaal niet in staat te zijn iets te vertegenwoordigen van belang); en de voormalige waarde opgeslagen valse precisie , wat een verspilling van onze eindige ruimte is (en inderdaad, het zou niet veel meer kosten om de waarde zo groot te maken dat de opslagcapaciteit overloopt).Een veelvoorkomend voorbeeld van wanneer dit formaat geschikt zou kunnen zijn, is voor een boekhoudsysteem:geldbedragen moeten gewoonlijk tot op de cent worden bijgehouden ongeacht hun grootte (daarom is minder precisie vereist voor kleine waarden en meer precisie voor grote waarden). Toevallig wordt valuta meestal ook als discreet beschouwd (penningen zijn ondeelbaar), dus dit is ook een goed voorbeeld van een situatie waarin een bepaalde basis (decimaal voor de meeste moderne valuta) wenselijk is om de hierboven besproken weergavefouten te voorkomen.
-
Drijvende punt , waarbij het aantal significante cijfers constant is, ongeacht de grootte :bijv. in een decimale weergave van 5 significante cijfers zouden onze waarden worden opgeslagen als
86.200
en0.000086200
(en per definitie beide keren 5 significante cijfers nauwkeurig hebben). In dit voorbeeld zijn beide waarden opgeslagen zonder enig verlies van precisie; en ze hebben allebei ook hetzelfde bedrag van valse precisie, wat minder verspillend is (en daarom kunnen we onze eindige ruimte gebruiken om een veel groter bereik aan waarden weer te geven, zowel grote als kleine).Een veelvoorkomend voorbeeld van wanneer dit formaat geschikt kan zijn, is voor het opnemen van alle metingen in de echte wereld :de precisie van meetinstrumenten (die allemaal last hebben van zowel systematische en willekeurig fouten) is redelijk constant, ongeacht de schaal, dus bij voldoende significante cijfers (meestal rond de 3 of 4 cijfers), gaat er absoluut geen precisie verloren zelfs als een verandering van grondtal resulteerde in afronding naar een ander getal .
Maar hoe nauwkeurig zijn de opslagformaten met drijvende komma gebruikt door onze computers?
-
Een IEEE754 enkele precisie (binary32) drijvende komma nummer heeft 24 bits, of
log10(2)
(meer dan 7) cijfers, van significantie, d.w.z. het heeft een tolerantie van minder dan±0.000006%
. Met andere woorden, het is nauwkeuriger dan te zeggen "86.20000
". -
Een IEEE754 dubbele precisie (binary64) drijvende komma nummer heeft 53 bits, of
log10(2)
(bijna 16) cijfers, van significantie, d.w.z. het heeft een tolerantie van iets meer dan±0.00000000000001%
. Met andere woorden, het is nauwkeuriger dan te zeggen "86.2000000000000
".
Het belangrijkste om te beseffen is dat deze formaten respectievelijk meer dan tienduizend . zijn en meer dan één biljoen keer precieser dan "86,2" te zeggen, ook al bevatten exacte conversies van het binaire getal terug naar decimaal foutieve valse precisie (die we moeten negeren:hierover binnenkort meer)!
-
Merk ook op dat beide opgelost en drijvende-komma-indelingen leiden tot verlies van precisie wanneer een waarde nauwkeuriger bekend is dan de indeling ondersteunt. Dergelijke afrondingsfouten
kan zich in rekenkundige bewerkingen voortplanten om schijnbaar foutieve resultaten op te leveren (wat ongetwijfeld uw verwijzing naar de "inherente onnauwkeurigheden" van drijvende-kommagetallen verklaart):bijvoorbeeld ⁄3 × 3000
op een vast punt van 5 plaatsen zou 999.99000
. opleveren in plaats van 1000.00000
; en ⁄7 − ⁄50
in 5-significante cijfers zou een drijvende komma 0.0028600
. opleveren in plaats van 0.0028571
.
Het veld van numerieke analyse is toegewijd aan het begrijpen van deze effecten, maar het is belangrijk om te beseffen dat elke bruikbaar systeem (zelfs het uitvoeren van berekeningen in je hoofd) is kwetsbaar voor dergelijke problemen omdat geen enkele berekeningsmethode die gegarandeerd zal eindigen ooit oneindige precisie kan bieden :denk bijvoorbeeld aan het berekenen van de oppervlakte van een cirkel - er zal noodzakelijkerwijs een verlies aan precisie zijn in de waarde die voor π wordt gebruikt, wat zich in het resultaat zal voortplanten.
Conclusie
-
Metingen in de echte wereld moeten binaire drijvende komma gebruiken :het is snel, compact, uiterst precies en niet slechter dan wat dan ook (inclusief de decimale versie waarmee je begon). Sinds De drijvende-komma-gegevenstypen van MySQL zijn IEEE754, dit is precies wat ze bieden.
-
Valuta-applicaties moeten denary vaste punt gebruiken :hoewel het traag is en geheugen verspilt, zorgt het ervoor dat waarden niet worden afgerond op onnauwkeurige hoeveelheden en dat er geen centen verloren gaan aan grote geldbedragen. Sinds vaste-punt datatypes van MySQL zijn BCD-gecodeerde strings, dit is precies wat ze bieden.
Houd er ten slotte rekening mee dat programmeertalen meestal fractionele waarden vertegenwoordigen met behulp van binaire drijvende komma typen:dus als uw database waarden in een ander formaat opslaat, moet u voorzichtig zijn met hoe ze in uw toepassing worden gebracht, anders kunnen ze worden geconverteerd (met alle problemen van dien) in de interface.
Welke optie is in dit geval het beste?
Hopelijk heb ik je ervan overtuigd dat je waarden veilig (en moeten) ) worden opgeslagen in drijvende-kommatypes zonder al te veel zorgen te maken over eventuele "onnauwkeurigheden"? Denk eraan, ze zijn meer nauwkeuriger is dan uw magere decimale weergave van drie significante cijfers ooit was:u hoeft alleen valse precisie te negeren (maar men moet altijd doe dat toch, zelfs als u een decimale notatie met vaste komma gebruikt).
Wat betreft uw vraag:kies optie 1 of 2 boven optie 3 - het maakt vergelijkingen gemakkelijker (om bijvoorbeeld de maximale massa te vinden, kunt u gewoon MAX(mass)
gebruiken , terwijl om het efficiënt over twee kolommen te doen wat nesting zou vereisen).
Tussen die twee maakt het niet uit welke je kiest - drijvende-kommagetallen worden opgeslagen met een constant aantal significante bits ongeacht hun schaal .
Bovendien kan het in het algemeen voorkomen dat sommige waarden worden afgerond op binaire getallen die dichter bij hun oorspronkelijke decimale weergave liggen met optie 1, terwijl andere tegelijkertijd worden afgerond op binaire getallen die dichter bij hun oorspronkelijke decimale weergave liggen met optie 2, zoals we zullen binnenkort zien dat dergelijke weergavefouten zich alleen manifesteren binnen de valse precisie die altijd moet worden genegeerd.
Echter, in dit Omdat het gebeurt dat er 16 ounces tot 1 pond zijn (en 16 is een macht van 2), zijn de relatieve verschillen tussen de oorspronkelijke decimale waarden en opgeslagen binaire getallen met behulp van de twee benaderingen identiek :
-
5.387510
(niet5.3367187510
zoals vermeld in uw vraag) zou worden opgeslagen in een binary32 float als101.0110001100110011001102
(dat is5.3874998092651367187510
):dit is0.0000036%
van de oorspronkelijke waarde (maar, zoals hierboven besproken, was de "oorspronkelijke waarde" al een behoorlijk slechte weergave van de fysieke hoeveelheid die het vertegenwoordigt).Wetende dat een binary32 float slechts 7 decimale cijfers nauwkeurig opslaat, weet onze compiler het zeker dat alles vanaf het 8e cijfer zeker . is valse precisie en daarom moet worden genegeerd in elke case—dus op voorwaarde dat onze invoerwaarde niet meer precisie vereiste dan dat (en als dat zo was, was binary32 duidelijk de verkeerde formaatkeuze), gegarandeerd een terugkeer naar een decimale waarde die er net zo rond uitziet als die waarmee we begonnen:
5.38750010
. We moeten echter echt domeinkennis toepassen op dit punt (zoals we zouden moeten doen met elk opslagformaat) om eventuele verdere valse precisie die zou kunnen bestaan, zoals die twee nullen aan het eind, weg te gooien. -
86.210
zou worden opgeslagen in een binary32 float als1010110.001100110011001102
(dat is86.199996948242187510
):dit is ook0.0000036%
van de oorspronkelijke waarde. Net als voorheen negeren we valse precisie om terug te keren naar onze oorspronkelijke invoer.
Merk op hoe de binaire representaties van de getallen identiek zijn, behalve de plaatsing van het radixpunt (wat vier bits uit elkaar ligt):
101.0110 00110011001100110 101 0110.00110011001100110
Dit komt omdat 5,3875 × 2 =86,2.