15. Lebegőpontos aritmetika: kérdések és korlátozások¶

a lebegőpontos számok a számítógépes hardverben 2. (bináris)frakcióként jelennek meg. Például a decimális frakció

0.125

értéke 1/10 + 2/100 + 5/1000, és ugyanígy a bináris frakció

0.001

értéke 0/2 + 0/4 + 1/8. Ennek a két frakciónak azonos értékei vannak,az egyetlenvalódi különbség az, hogy az elsőt 10 frakcionált jelölésben írják, a második pedig a 2.bázisban.

sajnos a legtöbb tizedes tört nem ábrázolható pontosan binaryfractions-ként. Ennek az a következménye, hogy általában a megadott decimális lebegőpontos számokat csak a gépben ténylegesen tárolt bináris lebegőpontos számokkal közelítik meg.

a problémát először könnyebb megérteni a 10-es alapban. Tekintsük a frakcionált1 / 3. Akkor hozzávetőleges, hogy a bázis 10 frakció:

0.3

vagy, jobb,

0.33

vagy, jobb,

0.333

stb. Nem számít, hány számjegyet hajlandó leírni, az eredménysoha nem lesz pontosan 1/3, de egyre jobb közelítés lesz1 / 3.

ugyanúgy, függetlenül attól, hogy hány bázis 2 számjegyet hajlandó használni, a 0.1 decimális értéket nem lehet pontosan bázisként ábrázolni 2 frakció. A base2-ben az 1/10 a végtelenül ismétlődő

0.0001100110011001100110011001100110011001100110011...

megáll minden véges bitszámnál, és kapsz egy közelítést. Ma a legtöbb gépen az úszók egy bináris frakcióval közelítetteka számláló az első 53 bitet használja, kezdve a legjelentősebb bittel ésa nevezővel, mint két erővel. 1/10 esetén a bináris fraktionis 3602879701896397 / 2 ** 55, amely közel van, de nem pontosan egyenlő az 1/10 valódi értékével.

sok felhasználó nem ismeri a közelítést, mert az értékek le vannak tévesztve. Python csak kiírja a decimális közelítés a valódi decimalvalue a bináris közelítés által tárolt gép. A legtöbb gépen, ifPython volt, hogy nyomtassa ki a valódi decimális értéke a bináris közelítés storedfor 0.1, akkor a kijelző

>>> 0.10.1000000000000000055511151231257827021181583404541015625

több számjegy, mint a legtöbb ember hasznos, szóval Piton tartja, hogy a több számjegy kezelhető megjelenítésével egy kerekített érték helyett

>>> 1 / 100.1

ne feledd, bár a nyomtatott eredmény úgy tűnik, a pontos valueof 1/10, az általában a tárolt érték a legközelebbi ábrázolható bináris tört.

érdekes módon sok különböző decimális szám van, amelyek azonos közelítő bináris frakcióval rendelkeznek. Például a 0.1 és0.10000000000000001 és0.1000000000000000055511151231257827021181583404541015625 számok a 3602879701896397 / 2 ** 55allamproximált számok. Mivel ezek a tizedesértékek azonos közelítéssel rendelkeznek, bármelyik megjeleníthető, miközben megőrzi az invariáns eval(repr(x)) == x.

történelmileg a Python prompt és a beépített repr() függvény a 17 jelentős számjegyű, 0.10000000000000001függvényt választaná. Kezdés: Python 3.1, A Python (a legtöbb rendszeren) most már képes kiválasztani a legrövidebbetezek egyszerűen megjelenítik a 0.1.

vegye figyelembe, hogy ez a bináris lebegőpont természeténél fogva: ez nem egy bugin Python, és nem is egy hiba a kódban. A hardver lebegőpontos aritmetikáját támogató összes nyelvben ugyanazt fogja látni(bár egyes nyelvek alapértelmezés szerint nem jelenítik meg a különbséget, vagy alloutput módban).

a kellemesebb kimenet érdekében érdemes lehet a karakterlánc formázását használni korlátozott számú jelentős számjegy előállításához:

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

fontos felismerni, hogy ez valódi értelemben illúzió: egyszerűen kerekíti a valódi gépérték megjelenítését.

az egyik illúzió egy másik lehet. Például, mivel 0.1 nem pontosan 1/10,összefoglalva három érték 0, 1 nem eredményezhet pontosan 0.3, vagy:

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

Is, mivel a 0.1 nem közelebb, hogy a pontos érték 1/10 and0.3 nem közelebb, hogy a pontos értéket a 3/10, akkor előre kerekítés around() funkció nem segít:

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

Bár a számokat nem lehet közelebb a tervezett pontos értékek,a round() funkció hasznos lehet a poszt-kerekítés, így resultswith pontatlan értékeket vált, hasonló egy másik:

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

Bináris lebegőpontos aritmetikai tartogat sok meglepetést, mint ez. A “0.1” – es problémát az alábbiakban, a “Reprezentációs hiba”szakaszban részletesen ismertetjük. Lásd a lebegő pont Veszélyeitegy teljesebb beszámolót más közös meglepetésekről.

ahogy a vége felé mondja: “nincsenek könnyű válaszok.”Mégis, ne légy undulywary a lebegőpontos! A Python float műveletek hibái öröklődneka lebegőpontos hardverből, a legtöbb gépen pedig a nomore sorrendben van, mint 1 rész 2 * * 53 műveletenként. Ez több, mint megfelelő a legtöbbfeladatok, de nem kell szem előtt tartani, hogy ez nem decimális számtani éshogy minden úszó művelet szenvedhet egy új kerekítési hiba.

míg a kóros esetek léteznek, a lebegőpontos számtani számok legtöbb hétköznapi használatához a végén látni fogja az elvárt eredményt, ha egyszerűen kerekíti a végeredményt a várt tizedesjegyek számához.str()általában elegendő, a finomabb vezérléshez lásd a str.format() metódus formátumspecifikusait a Formátum karakterlánc szintaxisában.

a pontos decimális ábrázolást igénylő Használati eseteknél próbálja meg a decimal modult használni, amely decimális aritmetikát alkalmazásokhoz és nagy pontosságú alkalmazásokhoz.

a pontos aritmetika egy másik formáját afractions module támogatja, amely racionális számokon alapuló aritmetikát valósít meg (tehát a hasonló számok 1/3 pontosan ábrázolható).

Ha Ön a lebegőpontos műveletek nehéz felhasználója, nézze meg a numerikus Python csomagot és sok más csomagot a scipy projekt által nyújtott matematikai ésstatisztikai műveletekhez. Lásd: <https://scipy.org>.

a Python olyan eszközöket kínál, amelyek segíthetnek azokban a ritka esetekben, amikor valóban szeretné tudni az úszó pontos értékét. Afloat.as_integer_ratio() módszer az úszó értékét afrakcióként fejezi ki:

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

Mivel ez az arány pontos, használható losslessly újra az eredeti érték:

>>> x == 3537115888337719 / 1125899906842624True

A float.hex() a módszer fejezi ki úszó hexadecimális (base16), újra adni a pontos értéket tárolja a számítógép által:

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

Ez a pontos hexadecimális képviselet használható reconstructthe lebegőpontos érték pontosan:

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

mivel a reprezentáció pontos, hasznos a Python különböző verzióinak megbízható hordozásához (platformfüggetlenség), valamint az ugyanazon formátumot támogató más nyelvekkel (például Java és C99) történő adatcseréhez.

egy másik hasznos eszköz a math.fsum() funkció, amely segít enyhíteni a pontosságot az összegzés során. Az “elveszett számjegyeket” követi, mivel az értékeket egy futó összegre adják hozzá. Ez különbséget tehet az Általános pontosságbanúgy, hogy a hibák ne halmozódjanak fel arra a pontra, ahol befolyásolják a végösszeget:

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

15.1. Reprezentációs hiba¶

Ez a rész részletesen elmagyarázza a “0.1” példát, majd megmutatja, hogyan lehet elvégezniaz ilyen esetek pontos elemzése. A binaryfloating-point ábrázolás alapvető ismerete feltételezhető.

Reprezentációs hiba arra utal, hogy néhány (legtöbb, valójában)tizedes tört nem ábrázolható pontosan bináris (bázis 2) frakcióként.Ez a fő oka annak, hogy a Python (vagy Perl, C, C++, Java, Fortran, és manyothers) gyakran nem jeleníti meg a pontos decimális számot, amire számít.

miért van ez? Az 1/10 nem pontosan reprezentálható bináris frakcióként. Szinte mindengép ma (November 2000) használja IEEE – 754 lebegőpontos számtani, ésszinte minden platformon térkép Python úszik IEEE – 754 “kettős pontosság”. 754doubles tartalmaznak 53 bit pontossággal, így a bemenet a számítógép arra törekszik, hogyconvert 0.1 a legközelebbi frakció lehet a forma J/2 * * N, ahol J isan egész szám, amely pontosan 53 bit. Átírás

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

s emlékeztetve arra, hogy J-nek pontosan 53 bit (a >= 2**52 de < 2**53),a legjobb ár-érték N 56:

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

Ez az,, 56-os érték a N, hogy a levelek J pontosan 53 bit. A legjobb lehetséges érték J akkor, hogy hányados lekerekített:

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

Mivel a fennmaradó több mint a fele a 10 a legjobb közelítés obtainedby kerekítési:

>>> q+17205759403792794

Ezért a lehető legjobb közelítés 1/10 a 754-es dupla pontosság:

7205759403792794 / 2 ** 56

Elválasztó mind a számláló, valamint nevező a két csökkenti a frakció, hogy:

3602879701896397 / 2 ** 55

Megjegyezzük, hogy mivel felfelé, ez valójában egy kicsit nagyobb, mint 1/10;ha nem kerekítettük volna fel, a hányados egy kicsit kisebb lett volna, mint1/10. De semmiképpen sem lehet pontosan 1/10!

Tehát a számítógép nem “lát”, 1/10: mit lát a pontos frakció givenabove, a legjobb 754 dupla közelítés lehet:

>>> 0.1 * 2 ** 553602879701896397.0

Ha megszorzunk, hogy a frakció által 10**55, láthatjuk az értékét ki to55 decimális számjegy:

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

ami azt jelenti, hogy a pontos számot tárolja a számítógép egyenlő valamelyes decimális érték 0.1000000000000000055511151231257827021181583404541015625.Ahelyett, hogy megjelenítése a teljes decimális érték, több nyelven (includingolder változatai Python), kerek az eredmény 17 szignifikáns számjegy:

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

A fractions vagy decimal modulok hogy ezek a 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'

Vélemény, hozzászólás?

Az e-mail-címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük