15 . Flyttall Aritmetikk: Problemer og Begrensninger¶

Flyttall er representert i maskinvare som base 2 (binære)fraksjoner. Desimalfraksjonen

0.125

har verdi 1/10 + 2/100 + 5/1000, og på samme måte har den binære fraksjonen

0.001

verdi 0/2 + 0/4 + 1/8. Disse to fraksjonene har identiske verdier, den enesteekte forskjellen er at den første er skrevet i base 10 fraksjonal notasjon,og den andre i base 2.

dessverre kan de fleste desimalfraksjoner ikke representeres nøyaktig som binærfraksjoner. En konsekvens er at, generelt, desimal flyttall du skriver inn er bare tilnærmet av de binære flyttallenefaktisk lagret i maskinen.

problemet er lettere å forstå først i base 10. Vurder fraksjonen1 / 3. Du kan tilnærme det som en base 10 fraksjon:

0.3
0.33

eller, bedre,

0.333

og så videre. Uansett hvor mange siffer du er villig til å skrive ned, vil resultatet aldri være nøyaktig 1/3, men vil bli en stadig bedre tilnærming av1 / 3.

på samme måte, uansett hvor mange base 2-sifre du er villig til å bruke, kan den decimale verdien 0,1 ikke representeres nøyaktig som en base 2-brøkdel. I base2 er 1/10 den uendelig repeterende fraksjonen

0.0001100110011001100110011001100110011001100110011...

Stopp ved et begrenset antall biter, og du får en tilnærming. På de fleste maskiner i dag er flyter tilnærmet ved hjelp av en binær brøkdel medtelleren ved hjelp av de første 53 bitene som starter med den viktigste biten ogmed nevnen som en kraft på to. I tilfelle av 1/10, den binære fraksjonener 3602879701896397 / 2 ** 55 som er nær, men ikke nøyaktiglik den sanne verdien av 1/10.

Mange brukere er ikke klar over tilnærmingen på grunn av måten verdiene vises på. Python skriver bare ut en desimal tilnærming til den sanne desimalverdien av binær tilnærming lagret av maskinen. På de fleste maskiner skulle ifPython skrive ut den sanne desimalverdien av den binære tilnærmingen storedfor 0.1, det må vise

>>> 0.10.1000000000000000055511151231257827021181583404541015625

bare husk, selv om det trykte resultatet ser ut som den nøyaktige verdien av 1/10, er den faktiske lagrede verdien den nærmeste representable binære fraksjonen.

Interessant er det mange forskjellige desimaltall som deler det sammenærmeste omtrentlige binære brøkdel. For eksempel er tallene 0.1og0.10000000000000001og0.1000000000000000055511151231257827021181583404541015625alle omfattes av 3602879701896397 / 2 ** 55. Siden alle disse desimalverdiene deler samme tilnærming, kan noen av dem vises mens de fortsatt beholder invarianten eval(repr(x)) == x.

Historisk Sett Vil Python-ledeteksten og innebygd repr() – funksjonen velge en med 17 signifikante sifre, 0.10000000000000001. Starter medpython 3.1, Python (på de fleste systemer) kan nå velge den korteste ofthese og bare vise 0.1.

Merk at dette er i selve binary floating-point: dette er ikke en bugin Python, og det er heller ikke en feil i koden din. Du vil se samme type ofthing på alle språk som støtter maskinvarens flyttall aritmetikk(selv om enkelte språk ikke kan vise forskjellen som standard, eller i alloutput moduser).

for mer behagelig utgang, kan det være lurt å bruke strengformatering for å produsere et begrenset antall signifikante sifre:

>>> 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 viktig å innse at dette er, i en reell forstand, en illusjon: du ‘ bare avrunding visning av den sanne maskin verdi.

en illusjon kan værefå en annen. For eksempel, siden 0,1 ikke er nøyaktig 1/10,kan summere tre verdier på 0,1 ikke gi nøyaktig 0,3, enten:

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

også, siden 0,1 ikke kan komme nærmere den eksakte verdien av 1/10 og0.3 kan ikke komme nærmere den eksakte verdien av 3/10, og deretter avrundes med round() funksjonen kan ikke hjelpe:

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

selv om tallene ikke kan gjøres nærmere deres tiltenkte eksakte verdier,kan round() funksjonen være nyttig for etterrunding, slik at resultatermed unøyaktige verdier blir sammenlignbare med hverandre:

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

binær flytende punkt aritmetikk har mange overraskelser som dette. Problemet med » 0.1 «er forklart i presis detalj nedenfor, i Delen «Representasjonsfeil». Se Farene Ved Flyttallfor en mer komplett redegjørelse for andre vanlige overraskelser.

som det sier nær slutten, » det er ingen enkle svar .»Likevel, ikke vær undulywary av flytende punkt! Feilene I Python float-operasjoner arvesfra flyttallsmaskinvaren, og på de fleste maskiner er det i rekkefølge av nomore enn 1 del i 2 * * 53 per operasjon. Det er mer enn tilstrekkelig for mosttasks, men du må huske på at det ikke er desimal aritmetikk og at hver flytoperasjon kan lide en ny avrundingsfeil.

mens patologiske tilfeller eksisterer, for mest tilfeldig bruk av flytende pointaritmetic vil du se resultatet du forventer til slutt hvis du bare avrunder visningen av dine endelige resultater til antall desimaler du forventer.str() er vanligvis nok, og for finere kontroll sestr.format() metodens formatspesifikatorer I Formatstrengsyntaks.

for brukstilfeller som krever nøyaktig desimalrepresentasjon, prøv å brukedecimal modulen som implementerer desimal aritmetikk egnet for regnskapsapplikasjoner og høy presisjon applikasjoner.

en annen form for eksakt aritmetikk støttes av fractions modulensom implementerer aritmetikk basert på rasjonelle tall (slik at tallene som1/3 kan representeres nøyaktig).

hvis du er en tung bruker av flyttallsoperasjoner, bør du ta en titt på Den Numeriske Python-pakken og mange andre pakker for matematiske og statistiske operasjoner levert av SciPy-prosjektet. Se <https://scipy.org>.

Python gir verktøy som kan hjelpe på de sjeldne anledninger når du virkeligvil vite den nøyaktige verdien av en flyte. float.as_integer_ratio() – metoden uttrykker verdien av en flyte som afraction:

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

float.hex() metode uttrykker en flyte i heksadesimal (base16), og gir igjen den nøyaktige verdien lagret av datamaskinen din:

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

denne nøyaktige heksadesimale representasjonen kan brukes til å rekonstruere floatverdien nøyaktig:

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

siden representasjonen er nøyaktig, er det nyttig for pålitelig porting valuesacross ulike versjoner Av Python (platform independence) og exchangingdata med andre språk som støtter samme format (For Eksempel Java Og C99).

Et annet nyttig verktøy ermath.fsum() – funksjonen som hjelper mitigateloss-of-precision under summering. Den sporer «tapte sifre» som verdier er lagt til en løpende total. Det kan gjøre en forskjell i total nøyaktighetslik at feilene ikke akkumuleres til det punktet der de påvirkerfinal total:

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

15.1. Representasjon Feil¶

dette avsnittet forklarer «0.1» – eksemplet i detalj, og viser hvordan du kan utføre en nøyaktig analyse av saker som dette selv. Grunnleggende kjennskap til binærflytende punktrepresentasjon antas.

Representasjonsfeil refererer til det faktum at noen (mest, faktisk)desimalfraksjoner ikke kan representeres nøyaktig som binære (base 2) fraksjoner.Dette er hovedgrunnen Til At Python (Eller Perl, C, C++, Java, Fortran og manyothers) ofte ikke vil vise det nøyaktige desimaltallet du forventer.

Hvorfor det ? 1/10 er ikke akkurat representable som en binær brøkdel. Nesten allemaskiner i dag (November 2000) bruker ieee-754 flytende punkt aritmetikk, ognesten alle plattformer kart Python flyter TIL ieee-754 «double precision». 754doubles inneholder 53 biter av presisjon, så videre input datamaskinen forsøker åkonvertere 0,1 til nærmeste brøkdel det kan av formen J/2 * * N Hvor J eret heltall som inneholder nøyaktig 53 biter. Rewriting

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

som

J ~= 2**N / 10

og minner Om At J har nøyaktig 53 biter (er>= 2**52men< 2**53), den beste verdien for n er 56:

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

det vil si at 56 Er den eneste verdien for n Som Etterlater j med nøyaktig 53 biter. Den beste mulige verdien For J er da kvotienten avrundet:

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

1/10 i 754 dobbel presisjon er:

7205759403792794 / 2 ** 56

ved å dele både teller og nevner med to reduseres fraksjonen til:

3602879701896397 / 2 ** 55

vi rundet opp, dette er faktisk litt større enn 1/10;hvis vi ikke hadde rundet opp, ville kvotienten ha vært litt mindre enn1 / 10. Men i intet tilfelle kan det være nøyaktig 1/10!

så datamaskinen aldri «ser» 1/10: hva den ser er den eksakte fraksjonen gittover, den beste 754 dobbel tilnærming det kan få:

>>> 0.1 * 2 ** 553602879701896397.0

hvis vi multipliserer den fraksjonen med 10**55, kan vi se verdien ut til55 desimaltall:

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

betyr at det eksakte tallet som er lagret i datamaskinen, er lik desimalverdien 0.1000000000000000055511151231257827021181583404541015625.I stedet for å vise hele desimalverdien, mange språk (inkluderteldre versjoner Av Python), runde resultatet til 17 signifikante sifre:

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

modulene gjør disse

>>> 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'

Legg igjen en kommentar

Din e-postadresse vil ikke bli publisert. Obligatoriske felt er merket med *