8

Stavo cercando di scrivere una funzione per approssimare le radici quadrate (so che c'è il modulo matematico ... Voglio farlo io stesso), e mi stavo fregando per l'aritmetica in virgola mobile. Come puoi evitarlo?Come evitare errori in virgola mobile?

def sqrt(num): 
    root = 0.0 
    while root * root < num: 
     root += 0.01 
    return root 

Usando questo ha questi risultati:

>>> sqrt(4) 
2.0000000000000013 
>>> sqrt(9) 
3.00999999999998 

mi rendo conto che potrei semplicemente usare round(), ma voglio essere in grado di rendere questo davvero accurata. Voglio essere in grado di calcolare fino a 6 o 7 cifre. Questo non sarebbe possibile se mi sto arrotondando. Voglio capire come gestire correttamente i calcoli in virgola mobile in Python.

+0

Forse provare il modulo [decimale] (http://docs.python.org/2/library/decimal.html), che è progettato per la precisione? – Michael0x2a

risposta

15

Questo non ha davvero nulla a che fare con Python - si vedrebbe lo stesso comportamento in qualsiasi lingua usando l'aritmetica in virgola mobile binaria dell'hardware. Primo read the docs.

Dopo aver letto questo, capirai meglio che sei non aggiungendo un centesimo nel tuo codice. Questo è esattamente ciò che si sta aggiungendo:

>>> from decimal import Decimal 
>>> Decimal(.01) 
Decimal('0.01000000000000000020816681711721685132943093776702880859375') 

quella stringa mostra l'esatto valore decimale del binario galleggiante ("doppia precisione" in C) approssimazione al valore esatto decimali 0,01. La cosa che stai davvero aggiungendo è un po 'più grande di 1/100.

Controllare gli errori numerici a virgola mobile è il campo denominato "analisi numerica" ​​ed è un argomento molto ampio e complesso. Finché sei sorpreso dal fatto che i float sono solo approssimazioni ai valori decimali, usa il modulo decimal. Ciò porterà via un mondo di problemi "superficiali" per te. Ad esempio, data questa piccola modifica alla funzione:

from decimal import Decimal as D 

def sqrt(num): 
    root = D(0) 
    while root * root < num: 
     root += D("0.01") 
    return root 

poi:

>>> sqrt(4) 
Decimal('2.00') 
>>> sqrt(9) 
Decimal('3.00') 

Non è davvero più accurato, ma può essere meno sorprendente in semplici esempi perché ora è l'aggiunta di esattamente uno uno -centesimo.

Un'alternativa è quella di attenersi a carri e aggiungere qualcosa che è esattamente rappresentabili come float binario: i valori del modulo I/2**J. Ad esempio, anziché aggiungere 0.01, aggiungere 0.125 (1/8) o 0.0625 (1/16).

allora sotto "il metodo di Newton" per calcolare le radici quadrate ;-)

+0

Per la cronaca avevo letto i documenti e conoscevo già l'intera cosa con precisione in virgola mobile con l'archiviazione delle rappresentazioni binarie. Avevo dimenticato il metodo di Newton. Stai raccogliendo tutte le mie domande qui intorno! Il mio giorno fortunato quando hai scoperto SO. Mi chiedo come funzioni il modulo Decimal. C'è un modo per scoprirlo oltre a leggere la fonte? – Aerovistae

+1

Beh, 'decimal' è stato originariamente scritto in Python e ha lavorato sugli elenchi di cifre decimali (0, 1, 2, ..., 9). Molto emulando come facciamo l'aritmetica su carta! "Floating point" richiede semplicemente l'aggiunta di un esponente (decimale) alla rappresentazione, quindi è molto attento ;-) Il modulo 'decimal' corrente è codificato in C, ed è molto più oscuro :-( –

+0

come hai detto, ho provato per risolvere '4 - 3.2' usando il modulo decimale. a = decimale (4) b = decimale (3.2) ma a - b è risultato decimale (',7999999999999998223643160600') – Srinesh