15. Aritmetica în virgulă mobilă: probleme și limitări numerele în virgulă mobilă

sunt reprezentate în hardware-ul computerului ca fracții de bază 2 (binare). De exemplu, fracția zecimală

0.125

are valoare 1/10 + 2/100 + 5/1000, și în același mod fracția binară

0.001

are valoare 0/2 + 0/4 + 1/8. Aceste două fracții au valori identice, singuradiferența reală fiind că prima este scrisă în baza 10 notație fracționată, iar a doua în baza 2.

Din păcate, majoritatea fracțiilor zecimale nu pot fi reprezentate exact ca fracțiuni binaryfractions. O consecință este că, în general, numerele zecimale în virgulă mobilă pe care le introduceți sunt aproximate doar de numerele binare în virgulă flotantăde fapt stocate în mașină.

problema este mai ușor de înțeles la început în baza 10. Luați în considerare fracția1/3. Puteți aproxima că, ca o bază 10 fracțiune:

0.3

sau, mai bine,

0.33

sau, mai bine,

0.333

și așa mai departe. Nu contează cât de multe cifre sunteți dispus să scrie în jos, rezultatulva fi niciodată exact 1/3, dar va fi o aproximare din ce în ce mai bună of1/3.

în același mod, indiferent de câte cifre de bază 2 sunteți dispus să utilizați, valoarea decimală 0.1 nu poate fi reprezentată exact ca o fracție de bază 2. În base2, 1/10 este fracția care se repetă infinit

0.0001100110011001100110011001100110011001100110011...

opriți-vă la orice număr finit de biți și obțineți o aproximare. Pe majoritatea mașinilor de astăzi, plutitorii sunt aproximați folosind o fracție binară cucu numărătorul folosind primii 53 de biți începând cu cel mai semnificativ bit șicu numitorul ca putere de doi. În cazul lui 1/10, fracțiunea binară este 3602879701896397 / 2 ** 55 care este aproape, dar nu exact egală cu valoarea reală a lui 1/10.

mulți utilizatori nu sunt conștienți de aproximare din cauza modului în care valorile sunt afișate. Python imprimă doar o aproximare zecimală la adevărata valoare zecimală a aproximării binare stocate de mașină. Pe majoritatea mașinilor, ifPython ar imprima adevărata valoare zecimală a aproximării binare storedfor 0.1, ar trebui să afișeze

>>> 0.10.1000000000000000055511151231257827021181583404541015625

care sunt mai multe cifre decât majoritatea oamenilor consideră utile, astfel încât Python păstrează numărul de cifre gestionabil prin afișarea unei valori rotunjite în loc

>>> 1 / 100.1

amintiți-vă, chiar dacă rezultatul tipărit arată ca valoarea exactă a 1/10, valoarea reală stocată este cea mai apropiată fracție binară reprezentabilă.

interesant, există multe numere zecimale diferite care împărtășesc același lucrucea mai apropiată fracție binară aproximativă. De exemplu, numerele 0.1 și0.10000000000000001 și0.1000000000000000055511151231257827021181583404541015625 Sunt toate aproximate de 3602879701896397 / 2 ** 55. Deoarece toate aceste valori zecimale au aceeași aproximare, oricare dintre ele ar putea fi afișateîn timp ce păstrează invariantul eval(repr(x)) == x.

istoric, promptul Python și built-inrepr() funcția AR choosethe cu 17 cifre semnificative,0.10000000000000001. Începând cu pitonul 3.1, Python (pe cele mai multe sisteme) este acum posibilitatea de a alege cel mai scurt ofthese și pur și simplu afișa 0.1.

rețineți că acest lucru este în însăși natura binar în virgulă mobilă: acest lucru nu este un python bugin, și nu este un bug în codul, fie. Veți vedea același lucru în toate limbile care acceptă aritmetica în virgulă mobilă a hardware-ului dvs.(deși este posibil ca unele limbi să nu afișeze diferența în mod implicit sau în modurile alloutput).

pentru o ieșire mai plăcută, poate doriți să utilizați formatarea șirurilor pentru a produce un număr limitat de cifre semnificative:

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

este important să ne dăm seama că aceasta este, într-un sens real, o iluzie: pur și simplu rotunjiți afișarea adevăratei valori a mașinii.

o iluzie poate naște alta. De exemplu, deoarece 0,1 nu este exact 1/10,însumarea a trei valori de 0,1 poate să nu producă exact 0,3, fie:

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

De asemenea, deoarece 0,1 nu se poate apropia de valoarea exactă a 1/10 și0.3 nu se poate apropia de valoarea exactă a 3/10, apoiround() funcția nu poate ajuta:

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

deși numerele nu pot fi apropiate de valorile exacte intenționate,funcțiaround()poate fi utilă pentru post-rotunjire, astfel încât rezultatele cu valori inexacte să devină comparabile între ele:

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

aritmetica binară în virgulă mobilă are multe surprize ca aceasta. Problema cu ” 0.1 „este explicată în detaliu mai jos, în secțiunea”eroare de reprezentare”. Vedeți pericolele punctului Plutitorpentru o relatare mai completă a altor surprize comune.

după cum se spune aproape de sfârșit, „nu există răspunsuri ușoare.”Totuși, nu fi undulywary de virgulă mobilă! Erorile din operațiunile Float Python sunt moștenitede la hardware-ul în virgulă mobilă, iar pe majoritatea mașinilor sunt de ordinul a mai mult de 1 parte în 2**53 pe operație. Acest lucru este mai mult decât adecvat pentru majoritateasarcini, dar trebuie să rețineți că nu este aritmetică zecimală șică fiecare operație de plutire poate suferi o nouă eroare de rotunjire.

în timp ce există cazuri patologice, pentru cea mai mare parte a utilizării ocazionale a aritmeticii în virgulă mobilă veți vedea rezultatul pe care îl așteptați în cele din urmă dacă pur și simplu rotunjiți afișarea rezultatelor finale la numărul de cifre zecimale pe care le așteptați.str() de obicei este suficient, iar pentru un control mai fin vedeți str.format()specificatorii formatului metodei în sintaxa șirului de Format.

pentru cazurile de utilizare care necesită reprezentare zecimală exactă, încercați să utilizați modululdecimal care implementează aritmetica zecimală adecvată pentru aplicații contabile și aplicații de înaltă precizie.

o altă formă de aritmetică exactă este susținută defractions modulcare implementează aritmetica bazată pe numere raționale (astfel încât numerele de genul1 / 3 pot fi reprezentate exact).

Dacă sunteți un utilizator greu de operațiuni în virgulă mobilă, ar trebui să aruncați o privire la pachetul Python numeric și multe alte pachete pentru operații matematice și statistice furnizate de proiectul SciPy. A se vedea <https://scipy.org>.

Python oferă instrumente care vă pot ajuta în acele rare ocazii când într-adevăr doriți să cunoașteți valoarea exactă a unui flotor. Metodafloat.as_integer_ratio() exprimă valoarea unui flotor ca afracție:

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

deoarece raportul este exact, acesta poate fi folosit pentru a recrea fără pierderi valoarea originală:

>>> x == 3537115888337719 / 1125899906842624True

float.hex() metoda exprimă un flotor în hexazecimal (base16), oferind din nou valoarea exactă stocată de computer:

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

această reprezentare hexazecimală precisă poate fi utilizată pentru a reconstrui valoarea flotorului exact:

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

deoarece reprezentarea este exactă, este utilă pentru portarea fiabilă a valorilor în diferite versiuni ale Python (independența platformei) și schimbul de date cu alte limbi care acceptă același format (cum ar fi Java și C99).

un alt instrument util estemath.fsum() funcție care ajută la atenuarea pierderii de precizie în timpul însumării. Se urmărește „cifre pierdute” ca valori areadded pe un total de funcționare. Acest lucru poate face o diferență în precizia generală, astfel încât erorile să nu se acumuleze până la punctul în care acestea afectează totalul final:

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

15.1. Eroare de reprezentare¶

Această secțiune explică „0.1” exemplu în detaliu, și arată cum puteți performan analiză exactă de cazuri de genul asta singur. Familiaritatea de bază cu binaruleste asumată reprezentarea punctului plutitor.

eroarea de reprezentare se referă la faptul că unele (majoritatea, de fapt)fracții zecimale nu pot fi reprezentate exact ca fracții binare (baza 2).Acesta este motivul principal pentru care Python (sau Perl, C, C++, Java, Fortran și multe altele) nu va afișa adesea numărul zecimal exact pe care îl așteptați.

de ce este asta? 1/10 nu este exact reprezentabil ca o fracție binară. Aproape toatemașini astăzi (Noiembrie 2000) folosesc IEEE-754 aritmetică în virgulă mobilă, șiaproape toate platformele harta Python plutește la IEEE-754 „precizie dublă”. 754dublele conțin 53 de biți de precizie, astfel încât la intrare computerul se străduiește să convertească 0,1 la cea mai apropiată fracție pe care o poate de forma J/2**N unde J esteun număr întreg care conține exact 53 de biți. Rescrierea

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

ca

J ~= 2**N / 10

și reamintind că J are exact 53 de biți (is >= 2**52dar< 2**53), cea mai bună valoare pentru N este 56:

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

adică 56 este singura valoare pentru n care lasă j cu exact 53 de biți. Cea mai bună valoare posibilă pentru J este atunci acel coeficient rotunjit:

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

deoarece restul este mai mult de jumătate din 10, se obține cea mai bună aproximare prin rotunjire:

>>> q+17205759403792794

prin urmare, cea mai bună aproximare posibilă la 1/10 în 754 precizia dublă este:

7205759403792794 / 2 ** 56

împărțirea atât a numărătorului, cât și a numitorului la două reduce fracția la:

3602879701896397 / 2 ** 55

rețineți că, deoarece am rotunjit, acest lucru este de fapt un pic mai mare decât 1/10;dacă nu am fi rotunjit, coeficientul ar fi fost puțin mai mic decât1/10. Dar în niciun caz nu poate fi exact 1/10!

deci computerul nu „vede” 1/10: ceea ce vede este fracția exactă dată mai sus, cea mai bună aproximare dublă 754 pe care o poate obține:

>>> 0.1 * 2 ** 553602879701896397.0

dacă înmulțim acea fracție cu 10**55, putem vedea valoarea la55 cifre zecimale:

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

ceea ce înseamnă că numărul exact stocat în computer este egal cuvaloarea zecimală 0.1000000000000000055511151231257827021181583404541015625.În loc să afișeze valoarea zecimală completă, multe limbi (inclusivversiuni mai vechi ale Python), rotunjiți rezultatul la 17 cifre semnificative:

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

modulele fractionsșidecimalfac aceste 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'

Lasă un răspuns

Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *