15. Flydende punkt aritmetik: problemer og begrænsninger lp

flydende punktnumre er repræsenteret i computerudstyr som base 2 (binære)fraktioner. For eksempel decimalfraktionen

0.125

har værdi 1/10 + 2/100 + 5/1000, og på samme måde den binære fraktion

0.001

har værdi 0/2 + 0/4 + 1/8. Disse to fraktioner har identiske værdier, den enestevirkelig forskel er,at den første er skrevet i base 10 fraktioneret notation og den anden i base 2.

desværre kan de fleste decimalfraktioner ikke repræsenteres nøjagtigt som binaryfraktioner. En konsekvens er, at de decimale flydende punktnumre, du indtaster, generelt kun tilnærmes af de binære flydende punktnumre, der faktisk er gemt i maskinen.

problemet er lettere at forstå først i base 10. Overvej fraktionen1 / 3. Du kan tilnærme det som en base 10 fraktion:

0.3

eller, bedre,

0.333

og så videre. Uanset hvor mange cifre du er villig til at skrive ned, vil resultatet aldrig være nøjagtigt 1/3, men vil være en stadig bedre tilnærmelse af1/3.

på samme måde, uanset hvor mange base 2-cifre du er villig til at bruge, kan den decimalværdi 0.1 ikke repræsenteres nøjagtigt som en base 2-fraktion. I base2 er 1/10 den uendeligt gentagne fraktion

0.0001100110011001100110011001100110011001100110011...

Stop ved ethvert endeligt antal bits, og du får en tilnærmelse. På de fleste maskiner i dag tilnærmes floats ved hjælp af en binær fraktion medtælleren ved hjælp af de første 53 bits, der starter med den mest betydningsfulde bit og med nævneren som en kraft på to. I tilfælde af 1/10 er den binære fraktioner 3602879701896397 / 2 ** 55 som er tæt på, men ikke nøjagtigtsvarende til den sande værdi af 1/10.

mange brugere er ikke opmærksomme på tilnærmelsen på grund af den måde, værdierne vises på. Python udskriver kun en decimal tilnærmelse til den sande decimalværdi af den binære tilnærmelse gemt af maskinen. På de fleste maskiner, ifPython skulle udskrive den sande decimalværdi af den binære tilnærmelse storedfor 0.1, Skulle det vise

>>> 0.10.1000000000000000055511151231257827021181583404541015625

det er flere cifre, end de fleste finder nyttige, så Python holder antallet af cifre håndterbart ved at vise en afrundet værdi i stedet

husk bare, selvom det udskrevne resultat ligner den nøjagtige værdiaf 1/10, er den faktiske lagrede værdi den nærmeste repræsentative binære fraktion.

interessant er der mange forskellige decimaltal, der deler det sammenæreste omtrentlige binære fraktion. For eksempel er tallene 0.1 og0.10000000000000001 og0.1000000000000000055511151231257827021181583404541015625 alletilnærmet af 3602879701896397 / 2 ** 55. Da alle disse decimalværdier deler den samme tilnærmelse, kunne en af dem visesmens de stadig bevarer invarianten eval(repr(x)) == x.

historisk set Python prompt og indbyggetrepr() funktionen ville vælgeden med 17 signifikante cifre,0.10000000000000001. Begyndende medpython 3.1, Python (på de fleste systemer) er nu i stand til at vælge den korteste ofthese og blot vise 0.1.

Bemærk, at dette er i selve karakteren af binært flydende punkt: Dette er ikke en bugin Python, og det er heller ikke en fejl i din kode. Du kan se den samme slags ting på alle sprog, der understøtter dit udstyrs flydende aritmetik(selvom nogle sprog muligvis ikke viser forskellen som standard eller i alloutput-tilstande).

for mere behagelig output kan du bruge strengformatering til at producere et begrænset antal signifikante cifre:

>>> format(math.pi, '.12g') # give 12 significant digits'3.14159265359'>>> format(math.pi, '.2f') # give 2 digits after the point'3.14'>>> repr(math.pi)'3.141592653589793'

det er vigtigt at indse, at dette i en reel forstand er en illusion: du ‘ afrunder simpelthen visningen af den sande maskinværdi.

en illusion kan værefå en anden. For eksempel, da 0,1 ikke er nøjagtigt 1/10,kan opsummering af tre værdier på 0,1 muligvis ikke give nøjagtigt 0,3, enten:

>>> .1 + .1 + .1 == .3False

også, da 0,1 ikke kan komme tættere på den nøjagtige værdi af 1/10 OG0.3 kan ikke komme tættere på den nøjagtige værdi af 3/10, derefter pre-afrunding medround() funktion kan ikke hjælpe:

>>> round(.1, 1) + round(.1, 1) + round(.1, 1) == round(.3, 1)False

selvom tallene ikke kan gøres tættere på deres tilsigtede nøjagtige værdier,kan round() funktionen være nyttig til efterrunding, så resultatermed upræcise værdier bliver sammenlignelige med hinanden:

>>> round(.1 + .1 + .1, 10) == round(.3, 10)True

binær flydende aritmetik har mange overraskelser som denne. Problemetmed ” 0.1 “forklares detaljeret nedenfor i afsnittet”Repræsentationsfejl”. Se farerne ved flydende Punktfor en mere komplet redegørelse for andre almindelige overraskelser.

som det siger nær slutningen, ” der er ingen nemme svar.”Stadig, vær ikke uforskammet med flydende punkt! Fejlene i Python float operationer er arvetfra floating-point udstyr, og på de fleste maskiner er i størrelsesordenen nomere end 1 del i 2**53 pr.operation. Det er mere end tilstrækkeligt for de flesteopgaver, men du skal huske på, at det ikke er decimal aritmetik ogat hver float operation kan lide en ny afrundingsfejl.

mens patologiske tilfælde eksisterer, for mest afslappet brug af floating-pointaritmetic vil du se det resultat, du forventer i sidste ende, hvis du blot afrunderdisplay af dine endelige resultater til antallet af decimaler, du forventer.str()normalt nok, og for finere kontrol se str.format() metodens formatspecifikatorer i Formatstrengssyntaks.

for brugssager, der kræver nøjagtig decimalrepræsentation, kan du prøve at brugedecimal modul, der implementerer decimal aritmetik, der er egnet til regnskabsprogrammer og applikationer med høj præcision.

en anden form for nøjagtig aritmetik understøttes af fractions modulsom implementerer aritmetik baseret på rationelle tal (så tallene som1/3 kan repræsenteres nøjagtigt).

Hvis du er en tung bruger af flydende punktoperationer, skal du kigge på den numeriske Python-pakke og mange andre pakker til matematiske ogstatistiske operationer leveret af SciPy-projektet. Se <https://scipy.org>.Python giver værktøjer, der kan hjælpe i de sjældne tilfælde, når du virkeligvil vide den nøjagtige værdi af en float. float.as_integer_ratio() metode udtrykker værdien af en float som afraktion:

>>> x = 3.14159>>> x.as_integer_ratio()(3537115888337719, 1125899906842624)

da forholdet er nøjagtigt, kan det bruges til tabsfrit at genskabe teoriginal værdi:

65″>

float.hex()metode udtrykker en float i heksadecimal (base16), igen giver den nøjagtige værdi gemt af din computer:

>>> x.hex()'0x1.921f9f01b866ep+1'

denne præcise heksadecimale repræsentation kan bruges til at rekonstruere floatværdien nøjagtigt:

>>> x == float.fromhex('0x1.921f9f01b866ep+1')True

da repræsentationen er nøjagtig, er den nyttig til pålidelig portering af værdierpå tværs af forskellige versioner af Python (platformuafhængighed) og udvekslingdata med andre sprog, der understøtter det samme format (såsom Java og C99).

et andet nyttigt værktøj ermath.fsum() funktion, som hjælper mitigateloss-of-precision under summation. Det sporer “tabte cifre” som værdier ertilføjet til en løbende total. Det kan gøre en forskel i den samlede nøjagtighedså fejlene ikke akkumuleres til det punkt, hvor de påvirkerendelig total:

>>> sum( * 10) == 1.0False>>> math.fsum( * 10) == 1.0True

15.1. Repræsentationsfejlkrist

dette afsnit forklarer “0.1” – eksemplet i detaljer og viser, hvordan du kan udføreen nøjagtig analyse af sager som denne selv. Grundlæggende kendskab til binaryfloating-point repræsentation antages.

Repræsentationsfejl henviser til det faktum, at nogle (mest faktisk)decimalfraktioner ikke kan repræsenteres nøjagtigt som binære (base 2) fraktioner.Dette er hovedårsagen til, at Python (eller Perl, C, C++, Java, Fortran og manyothers) ofte ikke viser det nøjagtige decimaltal, du forventer.

hvorfor er det? 1/10 er ikke ligefrem repræsenteret som en binær fraktion. Næsten allemaskiner i dag (November 2000) bruger IEEE-754 flydende punkt aritmetik, ognæsten alle platforme kort Python flyder til IEEE-754 “dobbelt præcision”. 754dobbelt indeholder 53 bits præcision, så ved input stræber computeren efter atkonvertere 0,1 til den nærmeste fraktion, den kan af formen J/2**n, hvor J eret heltal indeholdende nøjagtigt 53 bit. Omskrivning

1 / 10 ~= J / (2**N)

as

men < 2**53), den bedste værdi for n er 56:

>>> 2**52 <= 2**56 // 10 < 2**53True

det vil sige, 56 er den eneste værdi for n, der efterlader J med nøjagtigt 53 bit. Den bedst mulige værdi for J er så den kvotient afrundet:

>>> q, r = divmod(2**56, 10)>>> r6

da resten er mere end halvdelen af 10, opnås den bedste tilnærmelseved afrunding:

>>> q+17205759403792794

derfor er den bedst mulige tilnærmelse til 1/10 i 754 dobbelt præcision er:

7205759403792794 / 2 ** 56

dividere både tælleren og nævneren med to reducerer fraktionen til:

bemærk, at da vi afrundede op, dette er faktisk lidt større end 1/10;hvis vi ikke havde afrundet, ville kvotienten have været lidt mindre end1/10. Men under ingen omstændigheder kan det være nøjagtigt 1/10!

så computeren aldrig “ser” 1/10: hvad den ser er den nøjagtige brøkdel, der er givetovenfor, den bedste 754 dobbelt tilnærmelse, den kan få:

>>> 0.1 * 2 ** 553602879701896397.0

Hvis vi multiplicerer denne brøkdel med 10**55, kan vi se værdien ud til55 decimaler:

>>> 3602879701896397 * 10 ** 55 // 2 ** 551000000000000000055511151231257827021181583404541015625

hvilket betyder, at det nøjagtige tal, der er gemt i computeren, er lig med decimal værdi 0.1000000000000000055511151231257827021181583404541015625.I stedet for at vise den fulde decimalværdi, mange sprog (inkludererældre versioner af Python), runde resultatet til 17 signifikante cifre:

>>> format(0.1, '.17f')'0.10000000000000001'

fractions og decimal moduler gør disse calculationseasy:

>>> from decimal import Decimal>>> from fractions import Fraction>>> Fraction.from_float(0.1)Fraction(3602879701896397, 36028797018963968)>>> (0.1).as_integer_ratio()(3602879701896397, 36028797018963968)>>> Decimal.from_float(0.1)Decimal('0.1000000000000000055511151231257827021181583404541015625')>>> format(Decimal.from_float(0.1), '.17')'0.10000000000000001'

Skriv et svar

Din e-mailadresse vil ikke blive publiceret. Krævede felter er markeret med *