Perché 1 // 0,01 == 99 in Python?


31

Immagino che questa sia una classica domanda di precisione in virgola mobile, ma sto cercando di avvolgere la mia testa attorno a questo risultato, eseguendo i 1//0.01rendimenti di Python 3.7.5 99.

Immagino che sia un risultato atteso, ma c'è un modo per decidere quando è più sicuro da usare int(1/f)piuttosto che 1//f?


4
Sì, è sempre più sicuro int (1 / f). Semplicemente perché // è la divisione FLOOR e la pensi erroneamente come ROUND.
Perdi Estaquel,


2
Non un duplicato. Questo può funzionare come previsto al 99,99% utilizzando sempre round()e mai //o int(). La domanda collegata riguarda il confronto float non ha nulla a che fare con il troncamento e nessuna soluzione così semplice.
Maxy

Risposte:


23

Se questa fosse una divisione con numeri reali, 1//0.01sarebbe esattamente 100. Dato che si tratta di approssimazioni in virgola mobile, tuttavia, 0.01è leggermente più grande di 1/100, il che significa che il quoziente è leggermente più piccolo di 100. È questo valore 99. che viene quindi pavimentato a 99.


3
Questo non riguarda la parte "c'è un modo per decidere quando è più sicuro".
Scott Hunter,

10
"Più sicuro" non è ben definito.
Chepner,

1
Abbastanza da ignorarlo completamente, esp. quando l'OP è a conoscenza di problemi in virgola mobile?
Scott Hunter,

3
@chepner Se "più sicuro" non è ben definito, forse è meglio chiedere un chiarimento: /

2
è abbastanza chiaro per me che "più sicuro" significa "errore non peggio di un calcolatore tascabile economico"
maxy

9

Le ragioni di questo risultato sono come si afferma e sono spiegate in La matematica in virgola mobile è rotta? e molte altre domande e risposte simili.

Quando conosci il numero di decimali di numeratore e denominatore, un modo più affidabile è moltiplicare prima quei numeri in modo che possano essere trattati come numeri interi e quindi eseguire la divisione di numeri interi su di essi:

Quindi nel tuo caso 1//0.01dovrebbe essere convertito prima in 1*100//(0.01*100)cui è 100.

In casi più estremi è ancora possibile ottenere risultati "imprevisti". Potrebbe essere necessario aggiungere una roundchiamata al numeratore e al denominatore prima di eseguire la divisione intera:

1 * 100000000000 // round(0.00000000001 * 100000000000)

Ma, se si tratta di lavorare con decimali fissi (denaro, centesimi), allora considera di lavorare con i centesimi come unità , in modo che tutta l'aritmetica possa essere eseguita come aritmetica intera e convertire solo in / dalla principale unità monetaria (dollaro) quando lo fai I / O.

In alternativa, utilizzare una libreria per i decimali, come i decimali , che:

... fornisce supporto per l'aritmetica decimale a virgola mobile rapidamente arrotondata correttamente.

from decimal import Decimal
cent = Decimal(1) / Decimal(100) # Contrary to floating point, this is exactly 0.01
print (Decimal(1) // cent) # 100

3
"che ovviamente è 100." Non necessariamente: se .01 non è esatto, allora .01 * 100 non lo è altrettanto. Deve essere "sintonizzato" manualmente.
glglgl

8

Quello che devi prendere in considerazione è che //è l' flooroperatore e come tale dovresti prima pensare come se avessi la stessa probabilità di cadere in 100 come in 99 (*) (perché l'operazione sarà effettuata 100 ± epsilona epsilon>0condizione che le probabilità di ottenere esattamente 100,00 ..0 sono estremamente bassi.)

Puoi effettivamente vedere lo stesso con un segno meno,

>>> 1//.01
99.0
>>> -1//.01
-100.0

e dovresti essere (non) sorpreso.

D'altra parte, int(-1/.01)esegue prima la divisione e quindi applica int()il numero, che non è piano ma un troncamento verso 0 ! nel senso che in quel caso,

>>> 1/.01
100.0
>>> -1/.01
-100.0

quindi,

>>> int(1/.01)
100
>>> int(-1/.01)
-100

L'arrotondamento, tuttavia, ti darebbe il TUO risultato atteso per questo operatore perché, di nuovo, l'errore è piccolo per quelle cifre.

(*) Non sto dicendo che la probabilità sia la stessa, sto solo dicendo che a priori quando esegui un tale calcolo con aritmetica mobile che è una stima di ciò che stai ottenendo.


7

I numeri in virgola mobile non possono rappresentare esattamente la maggior parte dei numeri decimali, quindi quando si digita un letterale in virgola mobile si ottiene effettivamente un'approssimazione di quel letterale. L'approssimazione può essere maggiore o minore del numero digitato.

È possibile visualizzare il valore esatto di un numero in virgola mobile eseguendolo su Decimale o Frazione.

>>> from decimal import Decimal
>>> Decimal(0.01)
Decimal('0.01000000000000000020816681711721685132943093776702880859375')
>>> from fractions import Fractio
>>> Fraction(0.01)
Fraction(5764607523034235, 576460752303423488) 

Possiamo usare il tipo di frazione per trovare l'errore causato dal nostro letterale inesatto.

>>> float((Fraction(1)/Fraction(0.01)) - 100)
-2.0816681711721685e-15

Possiamo anche scoprire come i numeri in virgola mobile a precisione doppia granulare intorno a 100 sono usando Nextafter di numpy.

>>> from numpy import nextafter
>>> nextafter(100,0)-100
-1.4210854715202004e-14

Da ciò possiamo ipotizzare che il numero in virgola mobile più vicino 1/0.01000000000000000020816681711721685132943093776702880859375sia in realtà esattamente 100.

La differenza tra 1//0.01e int(1/0.01)è l'arrotondamento. 1 // 0,01 arrotonda il risultato esatto al numero intero successivo in un solo passaggio. Quindi otteniamo un risultato di 99.

int (1 / 0.01) invece arrotonda in due fasi, prima arrotonda il risultato al numero in virgola mobile a precisione doppia più vicino (che è esattamente 100), quindi arrotonda il numero in virgola mobile al numero intero successivo (che è di nuovo esattamente 100).


Chiamare questo arrotondamento è fuorviante. Dovrebbe essere chiamato troncamento o arrotondamento verso zero : int(0.9) == 0eint(-0.9) == 0
maxy

Si tratta di tipi binari a virgola mobile di cui stai parlando qui. (Esistono anche tipi decimali in virgola mobile.)
Stephen C

3

Se si esegue quanto segue

from decimal import *

num = Decimal(1) / Decimal(0.01)
print(num)

L'output sarà:

99.99999999999999791833182883

È così che viene rappresentato internamente, quindi arrotondandolo per difetto //darà99


2
È abbastanza accurato da mostrare l'errore in questo caso, ma tieni presente che l'aritmetica "decimale" non è neanche esatta.
lavare il

Con Decimal(0.01)te è troppo tardi, l'errore è già comparso prima di chiamare Decimal. Non sono sicuro di come questa sia una risposta alla domanda ... Devi prima calcolare uno 0,01 preciso Decimal(1) / Decimal(100), come ho mostrato nella mia risposta.
trincot,

@trincot La mia risposta è alla domanda nel titolo "Why 1 // 0,01 == 99" Ho provato a mostrare all'OP come i numeri fluttuanti sono trattati internamente.
Pioggia il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.