La matematica in virgola mobile è rotta?


2983

Considera il seguente codice:

0.1 + 0.2 == 0.3  ->  false
0.1 + 0.2         ->  0.30000000000000004

Perché si verificano queste inesattezze?


127
Le variabili in virgola mobile in genere hanno questo comportamento. È causato dal modo in cui sono memorizzati nell'hardware. Per maggiori informazioni consulta l' articolo di Wikipedia sui numeri in virgola mobile .
Ben S,

62
JavaScript considera i decimali come numeri in virgola mobile , il che significa che operazioni come l'addizione potrebbero essere soggette ad errori di arrotondamento. Potresti voler dare un'occhiata a questo articolo: Ciò che ogni scienziato informatico dovrebbe sapere sull'aritmetica a virgola mobile
matt b

4
Solo per informazione, TUTTI i tipi numerici in JavaScript sono IEEE-754 doppi.
Gary Willoughby

6
Poiché JavaScript utilizza lo standard IEEE 754 per la matematica, utilizza numeri mobili a 64 bit . Ciò causa errori di precisione quando si eseguono calcoli in virgola mobile (decimale), in breve, a causa di computer che lavorano in Base 2 mentre il decimale è Base 10 .
Pardeep Jain,

Risposte:


2253

La matematica binaria in virgola mobile è così. Nella maggior parte dei linguaggi di programmazione, si basa sullo standard IEEE 754 . Il nocciolo del problema è che i numeri sono rappresentati in questo formato come un numero intero per una potenza di due; numeri razionali (come 0.1, che è 1/10) il cui denominatore non è un potere di due non può essere rappresentato esattamente.

Per 0.1nel binary64formato standard , la rappresentazione può essere scritta esattamente come

  • 0.1000000000000000055511151231257827021181583404541015625 in decimale o
  • 0x1.999999999999ap-4nella notazione hexfloat C99 .

Al contrario, il numero razionale 0.1, che è 1/10, può essere scritto esattamente come

  • 0.1 in decimale o
  • 0x1.99999999999999...p-4in un analogo della notazione hexfloat C99, dove ...rappresenta una sequenza infinita di 9.

Le costanti 0.2e 0.3nel tuo programma saranno anche approssimazioni ai loro veri valori. Succede che il più vicino doublea 0.2è più grande del numero razionale 0.2ma che il più vicino doublea 0.3è più piccolo del numero razionale 0.3. La somma di 0.1e 0.2finisce per essere più grande del numero razionale 0.3e quindi in disaccordo con la costante nel tuo codice.

Un trattamento abbastanza completo delle questioni aritmetiche in virgola mobile è quello che ogni scienziato informatico dovrebbe sapere sull'aritmetica in virgola mobile . Per una spiegazione più facile da digerire, vedere floating-point-gui.de .

Nota a margine: tutti i sistemi numerici posizionali (base-N) condividono questo problema con precisione

I numeri decimali vecchi (base 10) normali hanno gli stessi problemi, motivo per cui numeri come 1/3 finiscono con 0,333333333 ...

Ti sei appena imbattuto in un numero (3/10) che sembra essere facile da rappresentare con il sistema decimale, ma non si adatta al sistema binario. Va anche in entrambi i modi (in una certa misura): 1/16 è un brutto numero in decimale (0,0625), ma in binario sembra pulito come un decimo in decimale (0,0001) ** - se fossimo in l'abitudine di usare un sistema numerico di base 2 nella nostra vita quotidiana, guarderesti anche quel numero e capiresti istintivamente che potresti arrivare lì dimezzando qualcosa, dimezzandolo ancora e ancora e ancora e ancora.

** Certo, non è esattamente come i numeri a virgola mobile sono memorizzati nella memoria (usano una forma di notazione scientifica). Tuttavia, illustra il punto in cui gli errori binari di precisione in virgola mobile tendono ad insorgere perché i numeri del "mondo reale" con cui siamo solitamente interessati a lavorare sono così spesso potenze di dieci - ma solo perché usiamo un sistema di numeri decimali giorno - oggi. Questo è anche il motivo per cui diremo cose come il 71% invece di "5 su 7" (il 71% è un'approssimazione, poiché il 5/7 non può essere rappresentato esattamente con nessun numero decimale).

Quindi no: i numeri binari in virgola mobile non sono rotti, ma sono semplicemente imperfetti come qualsiasi altro sistema di numeri di base-N :)

Nota laterale: lavorare con i galleggianti durante la programmazione

In pratica, questo problema di precisione significa che è necessario utilizzare le funzioni di arrotondamento per arrotondare i numeri in virgola mobile a qualsiasi numero decimale a cui si è interessati prima di visualizzarli.

È inoltre necessario sostituire i test di uguaglianza con confronti che consentano una certa tolleranza, il che significa:

Do Non fareif (x == y) { ... }

Invece farlo if (abs(x - y) < myToleranceValue) { ... }.

dove absè il valore assoluto. myToleranceValuedeve essere scelto per la tua specifica applicazione - e avrà molto a che fare con la quantità di "spazio di manovra" che sei disposto a consentire e quale potrebbe essere il numero più grande che stai per confrontare (a causa della perdita di problemi di precisione ). Fai attenzione alle costanti di stile "epsilon" nella tua lingua preferita. Questi non devono essere usati come valori di tolleranza.


181
Penso che "una costante di errore" sia più corretta di "The Epsilon" perché non esiste "The Epsilon" che potrebbe essere utilizzato in tutti i casi. Epsilon diversi devono essere utilizzati in diverse situazioni. E la macchina epsilon non è quasi mai una buona costante da usare.
Rotsor

34
Non è del tutto vero che tutta la matematica in virgola mobile si basa sullo standard IEEE [754]. Ci sono ancora alcuni sistemi in uso che hanno il vecchio FP esadecimale IBM, ad esempio, e ci sono ancora schede grafiche che non supportano l'aritmetica IEEE-754. È vero per un'approssimazione ragionevolmente, comunque.
Stephen Canon,

19
Cray ha abbandonato la conformità IEEE-754 per la velocità. Java ha allentato anche la sua adesione come ottimizzazione.
Art Taylor,

28
Penso che dovresti aggiungere qualcosa a questa risposta su come i calcoli sulla moneta dovrebbero sempre, sempre con l'aritmetica a virgola fissa sugli interi , perché il denaro è quantizzato. (Potrebbe avere senso eseguire calcoli di contabilità interna in minuscole frazioni di un centesimo, o qualunque sia la tua unità di valuta più piccola - questo spesso aiuta ad esempio a ridurre l'errore di arrotondamento quando converti "$ 29,99 al mese" in un tasso giornaliero - ma dovrebbe essere ancora aritmetica a virgola fissa.)
zwol,

18
Fatto interessante: proprio questo 0.1 non essere rappresentato esattamente in virgola mobile binaria ha causato un famigerato bug del software missilistico Patriot che ha provocato la morte di 28 persone durante la prima guerra in Iraq.
hdl,

603

La prospettiva di un progettista di hardware

Credo che dovrei aggiungere la prospettiva di un progettista di hardware a questo poiché ho progettato e costruito hardware a virgola mobile. Conoscere l'origine dell'errore può aiutare a capire cosa sta succedendo nel software e, in definitiva, spero che questo aiuti a spiegare i motivi per cui si verificano e sembrano accumularsi errori in virgola mobile nel tempo.

1. Panoramica

Dal punto di vista ingegneristico, la maggior parte delle operazioni in virgola mobile presenterà qualche elemento di errore poiché all'hardware che esegue i calcoli in virgola mobile è richiesto solo un errore di meno della metà di un'unità all'ultimo posto. Pertanto, gran parte dell'hardware si fermerà con una precisione necessaria solo per produrre un errore di meno della metà di un'unità all'ultimo posto per una singola operazione che è particolarmente problematica nella divisione in virgola mobile. Ciò che costituisce una singola operazione dipende da quanti operandi prende l'unità. Per la maggior parte, sono due, ma alcune unità accettano 3 o più operandi. Per questo motivo, non vi è alcuna garanzia che operazioni ripetute comporteranno un errore desiderabile poiché gli errori si sommano nel tempo.

2. Standard

La maggior parte dei processori segue lo standard IEEE-754 ma alcuni usano denormalizzati o standard diversi. Ad esempio, esiste una modalità denormalizzata in IEEE-754 che consente la rappresentazione di numeri in virgola mobile molto piccoli a scapito della precisione. Quanto segue, tuttavia, coprirà la modalità normalizzata di IEEE-754, che è la modalità operativa tipica.

Nello standard IEEE-754, ai progettisti hardware è consentito qualsiasi valore di errore / epsilon purché sia ​​inferiore alla metà di un'unità nell'ultimo posto e il risultato deve essere solo meno della metà di un'unità nell'ultimo posto per un'operazione. Questo spiega perché quando ci sono operazioni ripetute, gli errori si sommano. Per la doppia precisione IEEE-754, questo è il 54 ° bit, poiché 53 bit sono usati per rappresentare la parte numerica (normalizzata), chiamata anche mantissa, del numero in virgola mobile (es. 5.3 in 5.3e5). Le sezioni successive approfondiranno le cause dell'errore hardware su varie operazioni in virgola mobile.

3. Causa dell'errore di arrotondamento nella divisione

La causa principale dell'errore nella divisione in virgola mobile sono gli algoritmi di divisione utilizzati per calcolare il quoziente. La maggior parte dei sistemi informatici calcola la divisione utilizzando la moltiplicazione per un inverso, principalmente in Z=X/Y,Z = X * (1/Y). Una divisione viene calcolata iterativamente, ovvero ogni ciclo calcola alcuni bit del quoziente fino al raggiungimento della precisione desiderata, che per IEEE-754 è qualsiasi cosa con un errore di meno di un'unità all'ultimo posto. La tabella dei reciproci di Y (1 / Y) è nota come tabella di selezione del quoziente (QST) nella divisione lenta e la dimensione in bit della tabella di selezione del quoziente è in genere la larghezza della radice, o un numero di bit di il quoziente calcolato in ogni iterazione, più alcuni bit di guardia. Per lo standard IEEE-754, doppia precisione (64 bit), sarebbe la dimensione della radice del divisore, più alcuni bit di protezione k, dove k>=2. Quindi, ad esempio, una tipica tabella di selezione del quoziente per un divisore che calcola 2 bit del quoziente alla volta (radix 4) sarebbe 2+2= 4bit (più alcuni bit opzionali).

3.1 Errore di arrotondamento divisione: approssimazione del reciproco

I reciproci nella tabella di selezione del quoziente dipendono dal metodo di divisione : divisione lenta come divisione SRT o divisione rapida come divisione Goldschmidt; ogni voce viene modificata secondo l'algoritmo di divisione nel tentativo di produrre il minor errore possibile. In ogni caso, tuttavia, tutti i reciproci sono approssimazionidel reciproco reale e introdurre qualche elemento di errore. Sia i metodi di divisione lenta che quelli di divisione rapida calcolano il quoziente in modo iterativo, ovvero ogni numero di bit del quoziente viene calcolato ogni passaggio, quindi il risultato viene sottratto dal dividendo e il divisore ripete i passaggi fino a quando l'errore è inferiore alla metà di uno unità all'ultimo posto. I metodi di divisione lenti calcolano un numero fisso di cifre del quoziente in ogni passaggio e sono generalmente meno costosi da costruire, mentre i metodi di divisione veloce calcolano un numero variabile di cifre per passo e sono generalmente più costosi da costruire. La parte più importante dei metodi di divisione è che la maggior parte di essi si affida alla ripetuta moltiplicazione per approssimazione di un reciproco, quindi sono soggetti a errori.

4. Errori di arrotondamento in altre operazioni: troncamento

Un'altra causa degli errori di arrotondamento in tutte le operazioni sono le diverse modalità di troncamento della risposta finale consentita da IEEE-754. C'è troncato, arrotondamento verso zero, arrotondamento al più vicino (impostazione predefinita), arrotondamento per difetto e arrotondamento per eccesso . Tutti i metodi introducono un elemento di errore inferiore a un'unità all'ultimo posto per una singola operazione. Con il passare del tempo e le operazioni ripetute, il troncamento aggiunge anche cumulativamente all'errore risultante. Questo errore di troncamento è particolarmente problematico nell'esponenziazione, che comporta una qualche forma di moltiplicazione ripetuta.

5. Operazioni ripetute

Poiché l'hardware che esegue i calcoli in virgola mobile deve solo produrre un risultato con un errore di meno della metà di un'unità nell'ultima posizione per una singola operazione, l'errore crescerà su operazioni ripetute se non osservato. Questo è il motivo per cui nei calcoli che richiedono un errore limitato, i matematici usano metodi come l'uso della cifra pari arrotondata al più vicino all'ultimo posto dell'IEEE-754, perché, nel tempo, è più probabile che gli errori si annullino a vicenda out, e Interval Arithmetic combinato con variazioni delle modalità di arrotondamento IEEE 754per prevedere gli errori di arrotondamento e correggerli. A causa del suo errore relativo basso rispetto ad altre modalità di arrotondamento, arrotondare alla cifra pari più vicina (all'ultimo posto), è la modalità di arrotondamento predefinita di IEEE-754.

Si noti che la modalità di arrotondamento predefinita, cifra arrotondata alla cifra pari più vicina all'ultimo posto , garantisce un errore di meno della metà di un'unità nell'ultima posizione per un'operazione. L'uso del troncamento, dell'arrotondamento e dell'arrotondamento da soli può comportare un errore maggiore di metà dell'unità nell'ultima posizione, ma meno di un'unità nell'ultima posizione, quindi queste modalità non sono consigliate a meno che non lo siano utilizzato in aritmetica a intervalli.

6. Riepilogo

In breve, la ragione fondamentale degli errori nelle operazioni in virgola mobile è una combinazione del troncamento nell'hardware e del troncamento di un reciproco in caso di divisione. Poiché lo standard IEEE-754 richiede solo un errore di meno della metà di un'unità nell'ultima posizione per una singola operazione, gli errori in virgola mobile su operazioni ripetute si sommano se non vengono corretti.


8
(3) è sbagliato. L'errore di arrotondamento in una divisione non è inferiore a un'unità all'ultimo posto, ma al massimo metà un'unità all'ultimo posto.
gnasher729,

6
@ gnasher729 Buona cattura. La maggior parte delle operazioni di base ha anche un errore inferiore a 1/2 di un'unità all'ultimo posto utilizzando la modalità di arrotondamento IEEE predefinita. Ha modificato la spiegazione e ha anche osservato che l'errore può essere maggiore di 1/2 di una ulp ma inferiore a 1 ulp se l'utente ignora la modalità di arrotondamento predefinita (questo è particolarmente vero nei sistemi embedded).
KernelPanik,

39
(1) I numeri in virgola mobile non presentano errori. Ogni valore in virgola mobile è esattamente quello che è. La maggior parte (ma non tutte) le operazioni in virgola mobile danno risultati inesatti. Ad esempio, non esiste un valore binario in virgola mobile esattamente uguale a 1.0 / 10.0. Alcune operazioni (ad esempio, 1.0 + 1.0) non danno risultati esatti dall'altro.
Solomon Slow

19
"La causa principale dell'errore nella divisione in virgola mobile, sono gli algoritmi di divisione utilizzati per calcolare il quoziente" è una cosa molto fuorviante da dire. Per una divisione conforme IEEE-754, l' unica causa di errore nella divisione in virgola mobile è l'incapacità del risultato di essere rappresentato esattamente nel formato del risultato; lo stesso risultato viene calcolato indipendentemente dall'algoritmo utilizzato.
Stephen Canon,

6
@Matt Siamo spiacenti per la risposta tardiva. Fondamentalmente è dovuto a problemi di risorse / tempo e compromessi. C'è un modo per fare una divisione lunga / più divisione "normale", si chiama divisione SRT con radix due. Tuttavia, ciò sposta e sottrae ripetutamente il divisore dal dividendo e richiede molti cicli di clock poiché calcola solo un bit del quoziente per ciclo di clock. Usiamo le tabelle dei reciproci in modo da poter calcolare più bit del quoziente per ciclo e realizzare efficaci compromessi di prestazioni / velocità.
KernelPanik,

463

Quando si converte .1 o 1/10 in base 2 (binario) si ottiene uno schema ripetuto dopo il punto decimale, proprio come provare a rappresentare 1/3 in base 10. Il valore non è esatto e quindi non è possibile farlo matematica esatta con esso usando i normali metodi a virgola mobile.


133
Ottima e breve risposta. Il motivo ripetuto sembra 0,00011001100110011001100110011001100110011001100110011 ...
Konstantin Chernov,

4
Questo non spiega perché non viene utilizzato un algoritmo migliore che non si converta in binari in primo luogo.
Dmitri Zaitsev,

12
Perché prestazioni. L'uso del binario è alcune migliaia di volte più veloce, perché è nativo per la macchina.
Joel Coehoorn,

7
Esistono metodi che producono valori decimali esatti. BCD (codice binario decimale) o varie altre forme di numero decimale. Tuttavia, entrambi sono più lenti (MOLTO più lenti) e occupano più spazio di archiviazione rispetto all'utilizzo in virgola mobile binaria. (ad esempio, il BCD compresso memorizza 2 cifre decimali in un byte. Sono 100 valori possibili in un byte che possono effettivamente memorizzare 256 valori possibili, o 100/256, che spreca circa il 60% dei possibili valori di un byte.)
Duncan C,

16
@Jacksonkr stai ancora pensando nella base-10. I computer sono base-2.
Joel Coehoorn,

307

La maggior parte delle risposte qui si rivolge a questa domanda in termini molto secchi e tecnici. Vorrei affrontarlo in termini comprensibili agli esseri umani normali.

Immagina di provare a tagliare le pizze. Hai un taglia pizza robotico in grado di tagliare le fette di pizza esattamente a metà. Può dimezzare un'intera pizza o può dimezzare una fetta esistente, ma in ogni caso, la metà è sempre esatta.

Quel taglia pizza ha movimenti molto fini, e se inizi con una pizza intera, poi dimezzala e continua a dimezzare la fetta più piccola ogni volta, puoi fare la metà 53 volte prima che la fetta sia troppo piccola anche per le sue abilità di alta precisione . A quel punto, non puoi più dimezzare quella porzione molto sottile, ma devi includerla o escluderla così com'è.

Ora, come faresti a tagliare tutte le fette in modo tale da aggiungere fino a un decimo (0,1) o un quinto (0,2) di una pizza? Pensaci davvero e prova a risolverlo. Puoi anche provare a usare una vera pizza, se hai a portata di mano un mitico cutter per pizza di precisione. :-)


I programmatori più esperti, ovviamente, conoscono la vera risposta, che è che non c'è modo di mettere insieme un decimo o quinto esatto della pizza usando quelle fette, non importa quanto finemente le tagli. Puoi fare un'approssimazione abbastanza buona, e se sommi l'approssimazione di 0,1 con l'approssimazione di 0,2, ottieni un'approssimazione abbastanza buona di 0,3, ma è comunque solo un'approssimazione.

Per i numeri a doppia precisione (che è la precisione che consente di dimezzare la pizza 53 volte), i numeri immediatamente inferiori e maggiori di 0,1 sono 0,09999999999999999167332731531132594682276248931884765625 e 0,10000000000000000555511111212121257827021181583404541015625. Il secondo è un po 'più vicino allo 0,1 rispetto al primo, quindi un parser numerico, dato un input di 0,1, favorirà il secondo.

(La differenza tra questi due numeri è la "porzione più piccola" che dobbiamo decidere di includere, il che introduce un pregiudizio verso l'alto, o di escludere, che introduce un pregiudizio verso il basso. Il termine tecnico per quella fetta più piccola è un ulp .)

Nel caso di 0,2, i numeri sono tutti uguali, appena aumentati di un fattore 2. Ancora una volta, favoriamo il valore leggermente superiore a 0,2.

Si noti che in entrambi i casi, le approssimazioni per 0,1 e 0,2 presentano una leggera inclinazione verso l'alto. Se aggiungiamo abbastanza di questi pregiudizi, spingeranno il numero sempre più lontano da ciò che vogliamo, e in effetti, nel caso di 0,1 + 0,2, il bias è abbastanza alto che il numero risultante non è più il numero più vicino a 0,3.

In particolare, 0,1 + 0,2 è in realtà 0,1000000000000000055511151231257827021181583404541015625 + 0,200000000000000011102230246251565404236316680908203125 = 0,3000099999999999999


PS Alcuni linguaggi di programmazione forniscono anche taglierine per pizza che possono dividere le fette in decimi esatti . Sebbene tali taglia pizza non siano rari, se ne hai accesso a uno, dovresti usarlo quando è importante essere in grado di ottenere esattamente un decimo o un quinto di una fetta.

(Originariamente pubblicato su Quora.)


3
Nota che ci sono alcune lingue che includono la matematica esatta. Un esempio è Scheme, ad esempio tramite GNU Guile. Vedi draketo.de/english/exact-math-to-the-rescue - questi mantengono la matematica come frazioni e si dividono solo alla fine.
Arne Babenhauserheide,

5
@FloatingRock In realtà, pochissimi linguaggi di programmazione tradizionali hanno numeri razionali integrati. Arne è una Schemer, come me, quindi queste sono cose su cui ci viziamo.
Chris Jester-Young,

5
@ArneBabenhauserheide Penso che valga la pena aggiungere che funzionerà solo con numeri razionali. Quindi, se stai facendo un po 'di matematica con numeri irrazionali come pi, dovresti memorizzarlo come multiplo di pi. Naturalmente, qualsiasi calcolo relativo a pi non può essere rappresentato come un numero decimale esatto.
Aidiakapi,

13
@connexo Ok. Come programmeresti il ​​tuo rotatore per pizza per ottenere 36 gradi? Che cosa è di 36 gradi? (Suggerimento: se sei in grado di definirlo in modo esatto, hai anche una taglierina per pizza a fette e un decimo). In altre parole, non puoi effettivamente avere 1/360 (un grado) o 1 / 10 (36 gradi) con solo virgola mobile binaria.
Chris Jester-Young,

12
@connexo Inoltre, "ogni idiota" non può ruotare una pizza esattamente di 36 gradi. Gli umani sono troppo inclini all'errore per fare qualcosa di così preciso.
Chris Jester-Young,

212

Errori di arrotondamento in virgola mobile. 0.1 non può essere rappresentato con precisione in base-2 come in base-10 a causa del fattore primo mancante di 5. Proprio come 1/3 impiega un numero infinito di cifre per rappresentare in decimale, ma è "0.1" in base-3, 0.1 prende un numero infinito di cifre in base-2 dove non in base-10. E i computer non hanno una quantità infinita di memoria.


133
i computer non hanno bisogno di una quantità infinita di memoria per ottenere 0,1 + 0,2 = 0,3 giusto
Pacerier

23
@Pacerier Certo, potrebbero usare due numeri interi di precisione illimitata per rappresentare una frazione, oppure potrebbero usare la notazione di citazione. È la nozione specifica di "binario" o "decimale" che rende questo impossibile - l'idea che tu abbia una sequenza di cifre binarie / decimali e, da qualche parte lì, un punto radicale. Per ottenere risultati razionali precisi avremmo bisogno di un formato migliore.
Devin Jeanpierre,

15
@Pacerier: Né in virgola mobile binaria né decimale possono memorizzare precisamente 1/3 o 1/13. I tipi decimali in virgola mobile possono rappresentare con precisione i valori della forma M / 10 ^ E, ma sono meno precisi dei numeri binari in virgola mobile di dimensioni simili quando si tratta di rappresentare la maggior parte delle altre frazioni . In molte applicazioni, è più utile avere una precisione maggiore con frazioni arbitrarie che avere una precisione perfetta con alcune "speciali".
supercat

13
@Pacerier Lo fanno se stanno memorizzando i numeri come float binari, che era il punto della risposta.
Mark Amery,

3
@chux: la differenza di precisione tra i tipi binari e decimali non è enorme, ma la differenza di 10: 1 nella precisione migliore e peggiore per i tipi decimali è molto maggiore della differenza 2: 1 con i tipi binari. Sono curioso di sapere se qualcuno ha costruito hardware o software scritto per operare in modo efficiente su uno dei tipi decimali, dal momento che nessuno dei due sembrerebbe suscettibile di un'implementazione efficiente in hardware o software.
supercat,

121

Oltre alle altre risposte corrette, potresti considerare di ridimensionare i tuoi valori per evitare problemi con l'aritmetica in virgola mobile.

Per esempio:

var result = 1.0 + 2.0;     // result === 3.0 returns true

... invece di:

var result = 0.1 + 0.2;     // result === 0.3 returns false

L'espressione 0.1 + 0.2 === 0.3ritorna falsein JavaScript, ma fortunatamente l'aritmetica dei numeri interi in virgola mobile è esatta, quindi gli errori di rappresentazione decimale possono essere evitati ridimensionando.

Come esempio pratico, per evitare problemi a virgola mobile in cui la precisione è fondamentale, si raccomanda 1 al denaro maniglia come un intero che rappresenta il numero di centesimi: 2550centesimi invece di 25.50dollari.


1 Douglas Crockford: JavaScript: le parti buone : appendice A - Parti orribili (pagina 105) .


3
Il problema è che la conversione stessa non è precisa. 16.08 * 100 = 1607.9999999999998. Dobbiamo ricorrere alla divisione del numero e alla conversione separatamente (come in 16 * 100 + 08 = 1608)?
Jason,

38
La soluzione qui è fare tutti i tuoi calcoli in numero intero quindi dividerli per la tua proporzione (100 in questo caso) e arrotondare solo quando presenti i dati. Ciò garantirà che i tuoi calcoli siano sempre precisi.
David Granado,

16
Giusto per puntare un po ': l'aritmetica intera è esatta solo in virgola mobile fino a un punto (gioco di parole inteso). Se il numero è maggiore di 0x1p53 (per usare la notazione in virgola mobile esadecimale di Java 7, = 9007199254740992), allora l'ulp è 2 in quel punto e quindi 0x1p53 + 1 viene arrotondato per difetto a 0x1p53 (e 0x1p53 + 3 viene arrotondato per eccesso a 0x1p53 + 4, a causa del round-to-even). MrGreen Ma sicuramente, se il tuo numero è inferiore a 9 quadrilioni, dovresti andare bene. :-P
Chris Jester-Young,

2
Jason, dovresti semplicemente arrotondare il risultato (int) (16.08 * 100 + 0.5)
Mikhail Semenov,

@CodyBugstein " Quindi, come si ottiene .1 + .2 per mostrare .3? " Scrivere una funzione di stampa personalizzata per posizionare il decimale nel punto desiderato.
Ron John

113

La mia risposta è piuttosto lunga, quindi l'ho suddivisa in tre sezioni. Poiché la domanda riguarda la matematica in virgola mobile, ho posto l'accento su ciò che la macchina fa realmente. Ho anche reso specifico alla doppia precisione (64 bit), ma l'argomento si applica ugualmente a qualsiasi aritmetica in virgola mobile.

Preambolo

Un numero di formato a virgola mobile binario (binary64) IEEE 754 rappresenta un numero del modulo

valore = (-1) ^ s * (1.m 51 m 50 ... m 2 m 1 m 0 ) 2 * 2 e-1023

a 64 bit:

  • Il primo bit è il bit del segno : 1se il numero è negativo, 0altrimenti 1 .
  • I successivi 11 bit sono l' esponente , che è sfalsato di 1023. In altre parole, dopo aver letto i bit dell'esponente da un numero a doppia precisione, è necessario sottrarre 1023 per ottenere la potenza di due.
  • I restanti 52 bit sono il significato (o mantissa). Nella mantissa, un "implicito" 1.è sempre 2 omesso poiché il bit più significativo di qualsiasi valore binario è 1.

1 - IEEE 754 consente il concetto di zero con segno - +0e -0sono trattati in modo diverso: 1 / (+0)è infinito positivo; 1 / (-0)è l'infinito negativo. Per valori zero, i bit mantissa e esponente sono tutti zero. Nota: i valori zero (+0 e -0) non sono esplicitamente classificati come denormali 2 .

2 - Questo non è il caso dei numeri denormali , che hanno un esponente offset di zero (e un implicito 0.). L'intervallo dei numeri denormali di doppia precisione è d min ≤ | x | ≤ d max , dove d min (il numero non zero rappresentabile più piccolo) è 2 -1023 - 51 (≈ 4,94 * 10 -324 ) e d max (il numero denormale più grande, per il quale la mantissa è costituita interamente da 1s) è 2 -1023 + 1 - 2 -1023 - 51 (≈ 2.225 * 10 -308 ).


Trasformare un numero di precisione doppia in binario

Esistono molti convertitori online per convertire un numero in virgola mobile a doppia precisione in binario (ad esempio su binaryconvert.com ), ma ecco un codice C # di esempio per ottenere la rappresentazione IEEE 754 per un numero a doppia precisione (separo le tre parti con due punti ( :) :

public static string BinaryRepresentation(double value)
{
    long valueInLongType = BitConverter.DoubleToInt64Bits(value);
    string bits = Convert.ToString(valueInLongType, 2);
    string leadingZeros = new string('0', 64 - bits.Length);
    string binaryRepresentation = leadingZeros + bits;

    string sign = binaryRepresentation[0].ToString();
    string exponent = binaryRepresentation.Substring(1, 11);
    string mantissa = binaryRepresentation.Substring(12);

    return string.Format("{0}:{1}:{2}", sign, exponent, mantissa);
}

Arrivare al punto: la domanda originale

(Passa alla fine della versione TL; DR)

Cato Johnston (l'interrogativo della domanda) ha chiesto perché 0,1 + 0,2! = 0,3.

Scritte in binario (con due punti che separano le tre parti), le rappresentazioni IEEE 754 dei valori sono:

0.1 => 0:01111111011:1001100110011001100110011001100110011001100110011010
0.2 => 0:01111111100:1001100110011001100110011001100110011001100110011010

Si noti che la mantissa è composta da cifre ricorrenti di 0011. Questo è fondamentale per spiegare perché non v'è alcun errore ai calcoli - 0.1, 0.2 e 0.3 non possono essere rappresentati in binario precisamente in un limitato numero di bit binari non più di 1/9, 1/3 o 1/7 può essere rappresentato appunto cifre decimali .

Si noti inoltre che possiamo ridurre la potenza dell'esponente di 52 e spostare il punto nella rappresentazione binaria a destra di 52 posizioni (in modo simile a 10 -3 * 1,23 == 10 -5 * 123). Questo ci consente quindi di rappresentare la rappresentazione binaria come il valore esatto che rappresenta nella forma a * 2 p . dove 'a' è un numero intero.

Convertendo gli esponenti in decimale, rimuovendo l'offset e aggiungendo nuovamente gli impliciti 1(tra parentesi quadre), 0.1 e 0.2 sono:

0.1 => 2^-4 * [1].1001100110011001100110011001100110011001100110011010
0.2 => 2^-3 * [1].1001100110011001100110011001100110011001100110011010
or
0.1 => 2^-56 * 7205759403792794 = 0.1000000000000000055511151231257827021181583404541015625
0.2 => 2^-55 * 7205759403792794 = 0.200000000000000011102230246251565404236316680908203125

Per aggiungere due numeri, l'esponente deve essere lo stesso, ovvero:

0.1 => 2^-3 *  0.1100110011001100110011001100110011001100110011001101(0)
0.2 => 2^-3 *  1.1001100110011001100110011001100110011001100110011010
sum =  2^-3 * 10.0110011001100110011001100110011001100110011001100111
or
0.1 => 2^-55 * 3602879701896397  = 0.1000000000000000055511151231257827021181583404541015625
0.2 => 2^-55 * 7205759403792794  = 0.200000000000000011102230246251565404236316680908203125
sum =  2^-55 * 10808639105689191 = 0.3000000000000000166533453693773481063544750213623046875

Poiché la somma non è nella forma 2 n * 1. {bbb} aumentiamo l'esponente di uno e spostiamo il punto decimale ( binario ) per ottenere:

sum = 2^-2  * 1.0011001100110011001100110011001100110011001100110011(1)
    = 2^-54 * 5404319552844595.5 = 0.3000000000000000166533453693773481063544750213623046875

Ora ci sono 53 bit nella mantissa (il 53esimo è tra parentesi quadre nella riga sopra). La modalità di arrotondamento predefinita per IEEE 754 è " Arrotonda al più vicino ", ovvero se un numero x è compreso tra due valori a e b , viene scelto il valore in cui il bit meno significativo è zero.

a = 2^-54 * 5404319552844595 = 0.299999999999999988897769753748434595763683319091796875
  = 2^-2  * 1.0011001100110011001100110011001100110011001100110011

x = 2^-2  * 1.0011001100110011001100110011001100110011001100110011(1)

b = 2^-2  * 1.0011001100110011001100110011001100110011001100110100
  = 2^-54 * 5404319552844596 = 0.3000000000000000444089209850062616169452667236328125

Si noti che un e b differiscono solo per l'ultimo bit; ...0011+ 1= ...0100. In questo caso, il valore con il bit meno significativo di zero è b , quindi la somma è:

sum = 2^-2  * 1.0011001100110011001100110011001100110011001100110100
    = 2^-54 * 5404319552844596 = 0.3000000000000000444089209850062616169452667236328125

mentre la rappresentazione binaria di 0,3 è:

0.3 => 2^-2  * 1.0011001100110011001100110011001100110011001100110011
    =  2^-54 * 5404319552844595 = 0.299999999999999988897769753748434595763683319091796875

che differisce dalla rappresentazione binaria della somma di 0,1 e 0,2 solo per 2-54 .

Le rappresentazioni binarie di 0,1 e 0,2 sono le rappresentazioni più accurate dei numeri consentiti da IEEE 754. L'aggiunta di queste rappresentazioni, a causa della modalità di arrotondamento predefinita, produce un valore che differisce solo nel bit meno significativo.

TL; DR

Scrivendo 0.1 + 0.2in una rappresentazione binaria IEEE 754 (con due punti che separano le tre parti) e confrontandola 0.3, questo è (ho messo i bit distinti tra parentesi quadre):

0.1 + 0.2 => 0:01111111101:0011001100110011001100110011001100110011001100110[100]
0.3       => 0:01111111101:0011001100110011001100110011001100110011001100110[011]

Convertiti in decimali, questi valori sono:

0.1 + 0.2 => 0.300000000000000044408920985006...
0.3       => 0.299999999999999988897769753748...

La differenza è esattamente 2 -54 , che è ~ 5,5511151231258 × 10 -17 - insignificante (per molte applicazioni) rispetto ai valori originali.

Confrontare gli ultimi bit di un numero in virgola mobile è intrinsecamente pericoloso, come lo saprà chiunque legga il famoso " Ciò che ogni scienziato informatico dovrebbe sapere sull'aritmetica in virgola mobile " (che copre tutte le parti principali di questa risposta).

La maggior parte dei calcolatori utilizza cifre di guardia aggiuntive per aggirare questo problema, che è come 0.1 + 0.2darebbe 0.3: gli ultimi bit sono arrotondati.


14
La mia risposta è stata annullata poco dopo la sua pubblicazione. Da allora ho apportato molte modifiche (incluso notare esplicitamente i bit ricorrenti durante la scrittura di 0,1 e 0,2 in binario, che avevo omesso nell'originale). Nel caso in cui il down-voter lo veda, potresti per favore darmi qualche feedback in modo da poter migliorare la mia risposta? Sento che la mia risposta aggiunge qualcosa di nuovo poiché il trattamento della somma in IEEE 754 non è trattato allo stesso modo in altre risposte. Mentre "Quello che ogni scienziato informatico dovrebbe sapere ..." copre un po 'dello stesso materiale, la mia risposta riguarda specificamente il caso di 0,1 + 0,2.
Wai Ha Lee,

57

I numeri in virgola mobile memorizzati nel computer sono costituiti da due parti, un numero intero e un esponente in cui la base viene presa e moltiplicata per la parte intera.

Se il computer funzionasse nella base 10, 0.1sarebbe 1 x 10⁻¹, 0.2sarebbe 2 x 10⁻¹e 0.3sarebbe 3 x 10⁻¹. La matematica dei numeri interi è semplice ed esatta, quindi 0.1 + 0.2ovviamente l' aggiunta comporterà 0.3.

I computer di solito non funzionano nella base 10, funzionano nella base 2. È ancora possibile ottenere risultati esatti per alcuni valori, ad esempio 0.5è 1 x 2⁻¹ed 0.25è 1 x 2⁻², e aggiungendo risultati in 3 x 2⁻²o 0.75. Esattamente.

Il problema si presenta con numeri che possono essere rappresentati esattamente nella base 10, ma non nella base 2. Questi numeri devono essere arrotondati al loro equivalente più vicino. Supponendo il formato IEEE a virgola mobile a 64 bit molto comune, il numero più vicino a 0.1è 3602879701896397 x 2⁻⁵⁵e il numero più vicino a 0.2è 7205759403792794 x 2⁻⁵⁵; sommandoli insieme si ottiene 10808639105689191 x 2⁻⁵⁵un valore decimale esatto di 0.3000000000000000444089209850062616169452667236328125. I numeri in virgola mobile sono generalmente arrotondati per la visualizzazione.


2
@Mark Grazie per questa chiara spiegazione, ma poi sorge la domanda sul perché 0,1 + 0,4 aggiunge esattamente 0,5 (almeno in Python 3). Inoltre, qual è il modo migliore per verificare l'uguaglianza quando si usano i float in Python 3?
Pchegoor,

2
@ user2417881 Le operazioni in virgola mobile IEEE hanno regole di arrotondamento per ogni operazione, e talvolta l'arrotondamento può produrre una risposta esatta anche quando i due numeri sono spenti di un po '. I dettagli sono troppo lunghi per un commento e comunque non sono un esperto. Come vedi in questa risposta, 0,5 è uno dei pochi decimali che possono essere rappresentati in binario, ma è solo una coincidenza. Per i test di uguaglianza consultare stackoverflow.com/questions/5595425/… .
Mark Ransom,

1
@ user2417881 la tua domanda mi ha incuriosito, quindi l'ho trasformata in una domanda e risposta completa: stackoverflow.com/q/48374522/5987
Mark Ransom il

47

Errore di arrotondamento in virgola mobile. Da quello che ogni scienziato informatico dovrebbe sapere sull'aritmetica a virgola mobile :

La compressione infinita di molti numeri reali in un numero finito di bit richiede una rappresentazione approssimativa. Sebbene ci siano infiniti numeri interi, nella maggior parte dei programmi il risultato di calcoli di numeri interi può essere memorizzato in 32 bit. Al contrario, dato qualsiasi numero fisso di bit, la maggior parte dei calcoli con numeri reali produrrà quantità che non possono essere rappresentate esattamente usando quei tanti bit. Pertanto, il risultato di un calcolo in virgola mobile deve spesso essere arrotondato per rientrare nella sua rappresentazione finita. Questo errore di arrotondamento è la caratteristica del calcolo in virgola mobile.


33

La mia soluzione alternativa:

function add(a, b, precision) {
    var x = Math.pow(10, precision || 2);
    return (Math.round(a * x) + Math.round(b * x)) / x;
}

precisione si riferisce al numero di cifre che si desidera conservare dopo il punto decimale durante l'aggiunta.


30

Sono state pubblicate molte buone risposte, ma vorrei aggiungerne un'altra.

Non tutti i numeri possono essere rappresentati tramite float / doppi. Ad esempio, il numero "0,2" sarà rappresentato come "0,200000003" con precisione singola nello standard IEEE754 in virgola mobile.

Il modello per memorizzare i numeri reali sotto il cofano rappresenta i numeri float come

inserisci qui la descrizione dell'immagine

Anche se puoi digitare 0.2facilmente, FLT_RADIXed DBL_RADIXè 2; non 10 per un computer con FPU che utilizza "Standard IEEE per l'aritmetica binaria a virgola mobile (ISO / IEEE Std 754-1985)".

Quindi è un po 'difficile rappresentare esattamente tali numeri. Anche se si specifica esplicitamente questa variabile senza alcun calcolo intermedio.


28

Alcune statistiche relative a questa famosa domanda a doppia precisione.

Quando si aggiungono tutti i valori ( a + b ) utilizzando un passaggio di 0,1 (da 0,1 a 100), si ha ~ il 15% di probabilità di errore di precisione . Si noti che l'errore potrebbe comportare valori leggermente più grandi o più piccoli. Ecco alcuni esempi:

0.1 + 0.2 = 0.30000000000000004 (BIGGER)
0.1 + 0.7 = 0.7999999999999999 (SMALLER)
...
1.7 + 1.9 = 3.5999999999999996 (SMALLER)
1.7 + 2.2 = 3.9000000000000004 (BIGGER)
...
3.2 + 3.6 = 6.800000000000001 (BIGGER)
3.2 + 4.4 = 7.6000000000000005 (BIGGER)

Quando si sottraggono tutti i valori ( a - b dove a> b ) usando un passo di 0,1 (da 100 a 0,1) abbiamo ~ 34% di probabilità di errore di precisione . Ecco alcuni esempi:

0.6 - 0.2 = 0.39999999999999997 (SMALLER)
0.5 - 0.4 = 0.09999999999999998 (SMALLER)
...
2.1 - 0.2 = 1.9000000000000001 (BIGGER)
2.0 - 1.9 = 0.10000000000000009 (BIGGER)
...
100 - 99.9 = 0.09999999999999432 (SMALLER)
100 - 99.8 = 0.20000000000000284 (BIGGER)

* Il 15% e il 34% sono davvero enormi, quindi usa sempre BigDecimal quando la precisione è di grande importanza. Con 2 cifre decimali (passaggio 0,01) la situazione peggiora un po 'di più (18% e 36%).


28

No, non rotto, ma la maggior parte delle frazioni decimali deve essere approssimata

Sommario

L'aritmetica in virgola mobile è esatta, sfortunatamente, non si abbina bene con la nostra solita rappresentazione di numeri in base 10, quindi si scopre che spesso gli stiamo dando un input leggermente diverso da quello che abbiamo scritto.

Anche numeri semplici come 0,01, 0,02, 0,03, 0,04 ... 0,24 non sono rappresentabili esattamente come frazioni binarie. Se conti 0,01, 0,02, 0,03 ..., non fino a 0,25 otterrai la prima frazione rappresentabile nella base 2 . Se avessi provato a usare FP, il tuo 0,01 sarebbe stato leggermente spento, quindi l'unico modo per aggiungerne 25 fino a un bel 0,25 esatto avrebbe richiesto una lunga catena di causalità che includesse bit di guardia e arrotondamento. È difficile prevedere quindi alziamo le mani e diciamo "FP is inexact", ma non è proprio vero.

Diamo costantemente all'hardware FP qualcosa che sembra semplice nella base 10 ma è una frazione ripetitiva nella base 2.

Come è successo?

Quando scriviamo in decimale, ogni frazione (in particolare ogni decimale finale) è un numero razionale del modulo

           a / (2 n x 5 m )

In binario, otteniamo solo il termine 2 n , ovvero:

           a / 2 n

Quindi in decimale, non possiamo rappresentare 1 / 3 . Poiché la base 10 include 2 come fattore primo, ogni numero che possiamo scrivere come una frazione binaria può anche essere scritto come una frazione di base 10. Tuttavia, quasi nulla di ciò che scriviamo come frazione di base 10 è rappresentabile in binario. Nell'intervallo compreso tra 0,01, 0,02, 0,03 ... 0,99, nel nostro formato FP possono essere rappresentati solo tre numeri: 0,25, 0,50 e 0,75, poiché sono 1/4, 1/2 e 3/4, tutti i numeri con un fattore primo usando solo il termine 2 n .

In base di 10 non possiamo rappresentare 1 / 3 . Ma in binario, non possiamo farlo 1 / 10 o 1 / 3 .

Quindi, mentre ogni frazione binaria può essere scritta in decimale, non è vero il contrario. E infatti la maggior parte delle frazioni decimali si ripete in binario.

Trattare con esso

Di solito agli sviluppatori viene chiesto di fare < confronti di epsilon , un consiglio migliore potrebbe essere quello di arrotondare ai valori integrali (nella libreria C: round () e roundf (), cioè rimanere nel formato FP) e quindi confrontare. L'arrotondamento ad una lunghezza decimale specifica della frazione risolve la maggior parte dei problemi con l'output.

Inoltre, sui reali problemi di scricchiolio dei numeri (i problemi per i quali FP è stato inventato sui primi computer spaventosamente costosi) le costanti fisiche dell'universo e tutte le altre misurazioni sono note solo a un numero relativamente piccolo di cifre significative, quindi l'intero spazio del problema era "inesatto" comunque. "Precisione" FP non è un problema in questo tipo di applicazione.

L'intero problema sorge davvero quando le persone cercano di usare FP per il conteggio dei fagioli. Funziona per questo, ma solo se ti attieni ai valori integrali, che tipo di sconfigge il punto di usarlo. Questo è il motivo per cui abbiamo tutte quelle librerie di software con frazione decimale.

Adoro la risposta di Pizza di Chris , perché descrive il problema reale, non solo il solito giro di mani sull'imprecisione. Se FP fosse semplicemente "inaccurato", potremmo risolverlo e lo avremmo fatto decenni fa. La ragione per cui non l'abbiamo è perché il formato FP è compatto e veloce ed è il modo migliore per sgranocchiare molti numeri. Inoltre, è un'eredità dell'era spaziale e della corsa agli armamenti e dei primi tentativi di risolvere grossi problemi con computer molto lenti che utilizzano piccoli sistemi di memoria. (A volte, singoli nuclei magnetici per la memorizzazione a 1 bit, ma questo è un'altra storia. )

Conclusione

Se stai semplicemente contando i bean in una banca, le soluzioni software che utilizzano innanzitutto rappresentazioni di stringhe decimali funzionano perfettamente. Ma non puoi fare la cromodinamica quantistica o l'aerodinamica in quel modo.


L'arrotondamento al numero intero più vicino non è un modo sicuro per risolvere il problema di confronto in tutti i casi. 0.4999998 e 0.500001 arrotondano a numeri interi diversi, quindi esiste una "zona di pericolo" attorno a ogni punto di arrotondamento. (So ​​che quelle stringhe decimali probabilmente non sono esattamente rappresentabili come float binari IEEE.)
Peter Cordes

1
Inoltre, anche se il virgola mobile è un formato "legacy", è molto ben progettato. Non so nulla che qualcuno cambierebbe se lo riprogettasse ora. Più ne apprendo, più penso che sia davvero ben progettato. ad esempio l'esponente distorto significa che i float binari consecutivi hanno rappresentazioni di numeri interi consecutivi, quindi è possibile implementare nextafter()con un incremento o un decremento di interi sulla rappresentazione binaria di un float IEEE. Inoltre, puoi confrontare i galleggianti come numeri interi e ottenere la risposta giusta, tranne quando sono entrambi negativi (a causa della grandezza del segno rispetto al complemento di 2).
Peter Cordes,

Non sono d'accordo, i float dovrebbero essere memorizzati come decimali e non binari e tutti i problemi sono risolti.
Ronen Festinger,

" X / (2 ^ n + 5 ^ n) " non dovrebbe essere " x / (2 ^ n * 5 ^ n) "?
Wai Ha Lee,

@RonenFestinger - che ne dici di 1/3?
Stephen C,

19

Hai provato la soluzione del nastro adesivo?

Prova a determinare quando si verificano errori e correggili con brevi istruzioni if, non è carino ma per alcuni problemi è l'unica soluzione e questa è una di queste.

 if( (n * 0.1) < 100.0 ) { return n * 0.1 - 0.000000000000001 ;}
                    else { return n * 0.1 + 0.000000000000001 ;}    

Ho avuto lo stesso problema in un progetto di simulazione scientifica in c #, e posso dirti che se ignori l'effetto farfalla si trasformerà in un grosso drago grasso e ti morderà in un **


19

Per offrire la migliore soluzione posso dire di aver scoperto il seguente metodo:

parseFloat((0.1 + 0.2).toFixed(10)) => Will return 0.3

Lasciami spiegare perché è la soluzione migliore. Come altri menzionati nelle risposte sopra, è una buona idea utilizzare la funzione Javascript toFixed () pronta all'uso per risolvere il problema. Ma molto probabilmente incontrerai alcuni problemi.

Immagina di aggiungere due numeri float come 0.2ed 0.7eccolo qui:0.2 + 0.7 = 0.8999999999999999 .

Il risultato atteso è stato 0.9che significa che in questo caso è necessario un risultato con precisione a 1 cifra. Quindi avresti dovuto usare (0.2 + 0.7).tofixed(1) ma non puoi semplicemente dare un determinato parametro a ToFixed () poiché dipende dal numero dato, ad esempio

`0.22 + 0.7 = 0.9199999999999999`

In questo esempio hai bisogno di una precisione di 2 cifre, quindi dovrebbe essere toFixed(2) , quindi quale dovrebbe essere il parametro per adattarsi a ogni dato numero float?

Potresti dire che sia 10 in ogni situazione, quindi:

(0.2 + 0.7).toFixed(10) => Result will be "0.9000000000"

Dannazione! Che cosa hai intenzione di fare con quegli zeri indesiderati dopo le 9? È il momento di convertirlo in float per farlo come desideri:

parseFloat((0.2 + 0.7).toFixed(10)) => Result will be 0.9

Ora che hai trovato la soluzione, è meglio offrirla come una funzione come questa:

function floatify(number){
           return parseFloat((number).toFixed(10));
        }

Proviamo tu stesso:

function floatify(number){
       return parseFloat((number).toFixed(10));
    }
 
function addUp(){
  var number1 = +$("#number1").val();
  var number2 = +$("#number2").val();
  var unexpectedResult = number1 + number2;
  var expectedResult = floatify(number1 + number2);
  $("#unexpectedResult").text(unexpectedResult);
  $("#expectedResult").text(expectedResult);
}
addUp();
input{
  width: 50px;
}
#expectedResult{
color: green;
}
#unexpectedResult{
color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="number1" value="0.2" onclick="addUp()" onkeyup="addUp()"/> +
<input id="number2" value="0.7" onclick="addUp()" onkeyup="addUp()"/> =
<p>Expected Result: <span id="expectedResult"></span></p>
<p>Unexpected Result: <span id="unexpectedResult"></span></p>

Puoi usarlo in questo modo:

var x = 0.2 + 0.7;
floatify(x);  => Result: 0.9

Come W3SCHOOLS suggerisce che esiste anche un'altra soluzione, puoi moltiplicare e dividere per risolvere il problema sopra:

var x = (0.2 * 10 + 0.1 * 10) / 10;       // x will be 0.3

Tieni presente che (0.2 + 0.1) * 10 / 10non funzionerà affatto, anche se sembra lo stesso! Preferisco la prima soluzione poiché posso applicarla come una funzione che converte il float di input in float di output accurato.


questo mi ha fatto venire un forte mal di testa. Sommo 12 numeri float, quindi visualizzo la somma e la media se quei numeri. l'utilizzo di toFixed () potrebbe correggere la somma di 2 numeri, ma quando si sommano più numeri il salto è significativo.
Nuryagdy Mustapayev il

@Nuryagdy Mustapayev Non ho avuto la tua intenzione, come ho testato prima che tu potessi sommare 12 numeri float, quindi utilizzare la funzione floatify () sul risultato, quindi fare quello che vuoi su di esso, non ho riscontrato alcun problema nell'usarlo.
Mohammad Musavi il

Sto solo dicendo che nella mia situazione ho circa 20 parametri e 20 formule in cui il risultato di ogni formula dipende da altri questa soluzione non ha aiutato.
Nuryagdy Mustapayev il

16

Questi numeri strani compaiono perché i computer usano il sistema numerico binario (base 2) a scopo di calcolo, mentre noi usiamo i decimali (base 10).

Esiste una maggioranza di numeri frazionari che non possono essere rappresentati precisamente in binario o in decimale o entrambi. Risultato: un numero arrotondato (ma preciso) risulta.


Non capisco affatto il tuo secondo paragrafo.
Nae,

1
@Nae tradurrei il secondo paragrafo come "La maggior parte delle frazioni non può essere rappresentata esattamente in decimale o binario. Quindi la maggior parte dei risultati sarà arrotondata - anche se saranno comunque precisi al numero di bit / cifre inerenti alla rappresentazione in uso."
Steve Summit

15

Molti dei numerosi duplicati di questa domanda chiedono gli effetti dell'arrotondamento in virgola mobile su numeri specifici. In pratica, è più facile avere un'idea di come funziona osservando i risultati esatti dei calcoli di interesse piuttosto che leggendoli. Alcune lingue forniscono modi per farlo, come convertire un floato doubleinBigDecimal in Java.

Poiché si tratta di una domanda indipendente dalla lingua, necessita di strumenti indipendenti dalla lingua, come un convertitore da decimale a virgola mobile .

Applicandolo ai numeri nella domanda, trattati come doppi:

0,1 converte in 0.1000000000000000055511151231257827021181583404541015625,

0,2 viene convertito in 0,200000000000000011102230246251565404236316680908203125,

0,3 converte in 0,299999999999999988897769753748434595763683319091796875 e

0,30000000000000004 converte in 0,3000000000000000444089209850062616169452667236328125.

Aggiunta manuale dei primi due numeri o in una calcolatrice decimale come Calcolatrice di precisione completa , mostra che la somma esatta degli input effettivi è 0,3000000000000000166533453693773481063544750213623046875.

Se fosse arrotondato per difetto all'equivalente di 0,3, l'errore di arrotondamento sarebbe 0,0000000000000000277555756156289135105907917022705078125. L'arrotondamento per l'equivalente di 0,30000000000000004 dà anche un errore di arrotondamento 0,0000000000000000277555756156289135105907917022705078125. Si applica il pareggio tondo-uniforme.

Tornando al convertitore a virgola mobile, l'esadecimale grezzo per 0,30000000000000004 è 3fd333333333333334, che termina con una cifra pari e quindi è il risultato corretto.


2
Alla persona di cui ho appena eseguito il rollback della modifica: considero le virgolette del codice appropriate per la citazione del codice. Questa risposta, essendo indipendente dalla lingua, non contiene alcun codice tra virgolette. I numeri possono essere usati in frasi inglesi e ciò non li trasforma in codice.
Patricia Shanahan,

Questo è probabilmente il motivo per cui qualcuno ha formattato i tuoi numeri come codice, non per la formattazione, ma per la leggibilità.
Wai Ha Lee,

... inoltre, il round si riferisce anche alla rappresentazione binaria , non alla rappresentazione decimale . Vedi questo o, per esempio, questo .
Wai Ha Lee,

@WaiHaLee Non ho applicato il test pari / dispari a nessun numero decimale, ma solo esadecimale. Una cifra esadecimale è anche se, e solo se, il bit meno significativo della sua espansione binaria è zero.
Patricia Shanahan,

14

Dato che nessuno ha menzionato questo ...

Alcuni linguaggi di alto livello come Python e Java sono dotati di strumenti per superare le limitazioni binarie in virgola mobile. Per esempio:

  • decimalModulo Python e BigDecimalclasse Java , che rappresentano numeri internamente con notazione decimale (in contrapposizione alla notazione binaria). Entrambi hanno una precisione limitata, quindi sono ancora soggetti a errori, tuttavia risolvono i problemi più comuni con l'aritmetica binaria in virgola mobile.

    I decimali sono molto utili quando si tratta di denaro: dieci centesimi più venti centesimi sono sempre esattamente trenta centesimi:

    >>> 0.1 + 0.2 == 0.3
    False
    >>> Decimal('0.1') + Decimal('0.2') == Decimal('0.3')
    True
    

    Il decimalmodulo di Python si basa sullo standard IEEE 854-1987 .

  • fractionsModulo di Python e BigFractionclasse di Apache Common . Entrambi rappresentano numeri razionali come (numerator, denominator)coppie e possono dare risultati più precisi dell'aritmetica decimale in virgola mobile.

Nessuna di queste soluzioni è perfetta (soprattutto se guardiamo alle prestazioni o se richiediamo una precisione molto elevata), ma risolvono ancora un gran numero di problemi con l'aritmetica binaria in virgola mobile.


14

Posso solo aggiungere; la gente presume sempre che si tratti di un problema con il computer, ma se conti con le mani (base 10), non puoi ottenere a (1/3+1/3=2/3)=truemeno che tu non abbia l'infinito per aggiungere 0,333 ... a 0,333 ... così come con il (1/10+2/10)!==3/10problema nella base 2, lo tronchi a 0,333 + 0,333 = 0,666 e probabilmente lo arrotondi a 0,667 che sarebbe anche tecnicamente impreciso.

Conta in ternario, e i terzi non sono un problema però - forse una corsa con 15 dita per mano chiederebbe perché la tua matematica decimale è stata rotta ...


Dato che gli umani usano numeri decimali, non vedo alcuna buona ragione per cui i float non siano rappresentati come decimali di default, quindi abbiamo risultati accurati.
Ronen Festinger,

Gli umani usano molte basi diverse dalla base 10 (decimali), il binario è quello che usiamo di più per il calcolo .. la "buona ragione" è che non puoi semplicemente rappresentare ogni frazione in ogni base ..

L'aritmetica binaria di @RonenFestinger è facile da implementare sui computer perché richiede solo otto operazioni di base con cifre: diciamo $ a $, $ b $ in $ 0,1 $ tutto ciò che devi sapere è $ \ operatorname {xor} (a, b) $ e $ \ operatorname {cb} (a, b) $, dove xor è esclusivo oppure e cb è il "bit di trasporto" che è $ 0 $ in tutti i casi tranne quando $ a = 1 = b $, nel qual caso abbiamo uno (in effetti la commutatività di tutte le operazioni ti fa risparmiare $ 2 $ e tutto ciò di cui hai bisogno sono $ 6 $ regole). L'espansione decimale richiede $ 10 \ volte 11 $ (in notazione decimale) per essere archiviati e $ 10 $ stati diversi per ogni bit e spreca spazio sul carry.
Oskar Limka,

@RonenFestinger - Il decimale NON è più preciso. Questo è ciò che dice questa risposta. Per ogni base scelta, ci saranno numeri razionali (frazioni) che danno una sequenza di cifre ripetuta all'infinito. Per la cronaca, alcuni dei primi computer hanno usato le rappresentazioni di base 10 per i numeri, ma i pionieri progettisti di hardware per computer hanno presto concluso che la base 2 era molto più semplice ed efficiente da implementare.
Stephen C

9

Il tipo di matematica a virgola mobile che può essere implementata in un computer digitale utilizza necessariamente un'approssimazione dei numeri reali e delle operazioni su di essi. (La versione standard si estende su oltre cinquanta pagine di documentazione e ha un comitato per gestire i suoi errori e ulteriori perfezionamenti.)

Questa approssimazione è una miscela di approssimazioni di diverso tipo, ognuna delle quali può essere ignorata o spiegata con attenzione a causa del suo modo specifico di deviazione dall'esattezza. Implica anche una serie di casi eccezionali espliciti a livello sia hardware che software che la maggior parte delle persone passa davanti fingendo di non notare.

Se hai bisogno di una precisione infinita (usando il numero π, ad esempio, invece di uno dei suoi numerosi stand-in più brevi), dovresti invece scrivere o usare un programma matematico simbolico.

Ma se sei d'accordo con l'idea che a volte la matematica in virgola mobile è sfocata in termini di valore e la logica e gli errori possono accumularsi rapidamente e puoi scrivere i tuoi requisiti e test per consentirlo, allora il tuo codice può spesso cavarsela con ciò che è dentro la tua FPU.


9

Solo per divertimento, ho giocato con la rappresentazione dei float, seguendo le definizioni dello Standard C99 e ho scritto il codice qui sotto.

Il codice stampa la rappresentazione binaria dei float in 3 gruppi separati

SIGN EXPONENT FRACTION

e successivamente stampa una somma che, se sommata con sufficiente precisione, mostrerà il valore che esiste realmente nell'hardware.

Quindi, quando scrivi float x = 999..., il compilatore trasformerà quel numero in una rappresentazione di bit stampata dalla funzione in modo xxtale che la somma stampata dalla funzione yysia uguale al numero dato.

In realtà, questa somma è solo un'approssimazione. Per il numero 999.999.999 il compilatore inserirà nella rappresentazione in bit del float il numero 1.000.000.000

Dopo il codice allego una sessione di console, in cui calcolo la somma dei termini per entrambe le costanti (meno PI e 999999999) che esiste realmente nell'hardware, inserita lì dal compilatore.

#include <stdio.h>
#include <limits.h>

void
xx(float *x)
{
    unsigned char i = sizeof(*x)*CHAR_BIT-1;
    do {
        switch (i) {
        case 31:
             printf("sign:");
             break;
        case 30:
             printf("exponent:");
             break;
        case 23:
             printf("fraction:");
             break;

        }
        char b=(*(unsigned long long*)x&((unsigned long long)1<<i))!=0;
        printf("%d ", b);
    } while (i--);
    printf("\n");
}

void
yy(float a)
{
    int sign=!(*(unsigned long long*)&a&((unsigned long long)1<<31));
    int fraction = ((1<<23)-1)&(*(int*)&a);
    int exponent = (255&((*(int*)&a)>>23))-127;

    printf(sign?"positive" " ( 1+":"negative" " ( 1+");
    unsigned int i = 1<<22;
    unsigned int j = 1;
    do {
        char b=(fraction&i)!=0;
        b&&(printf("1/(%d) %c", 1<<j, (fraction&(i-1))?'+':')' ), 0);
    } while (j++, i>>=1);

    printf("*2^%d", exponent);
    printf("\n");
}

void
main()
{
    float x=-3.14;
    float y=999999999;
    printf("%lu\n", sizeof(x));
    xx(&x);
    xx(&y);
    yy(x);
    yy(y);
}

Ecco una sessione della console in cui computo il valore reale del float che esiste nell'hardware. Ho usato bcper stampare la somma di termini emessi dal programma principale. Si può inserire quella somma in Python replo anche qualcosa di simile.

-- .../terra1/stub
@ qemacs f.c
-- .../terra1/stub
@ gcc f.c
-- .../terra1/stub
@ ./a.out
sign:1 exponent:1 0 0 0 0 0 0 fraction:0 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 1 0 0 0 0 1 1
sign:0 exponent:1 0 0 1 1 1 0 fraction:0 1 1 0 1 1 1 0 0 1 1 0 1 0 1 1 0 0 1 0 1 0 0 0
negative ( 1+1/(2) +1/(16) +1/(256) +1/(512) +1/(1024) +1/(2048) +1/(8192) +1/(32768) +1/(65536) +1/(131072) +1/(4194304) +1/(8388608) )*2^1
positive ( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29
-- .../terra1/stub
@ bc
scale=15
( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29
999999999.999999446351872

Questo è tutto. Il valore di 999999999 è in effetti

999999999.999999446351872

Puoi anche verificare bcche -3.14 sia anche perturbato. Non dimenticare di impostare un scalefattore bc.

La somma visualizzata è ciò che è all'interno dell'hardware. Il valore che si ottiene calcolandolo dipende dalla scala impostata. Ho impostato il scalefattore su 15. Matematicamente, con precisione infinita, sembra che sia 1.000.000.000.


5

Un altro modo di vedere questo: sono usati 64 bit per rappresentare i numeri. Di conseguenza non è possibile rappresentare con precisione più di 2 ** 64 = 18.446.744.073.709.551.616 numeri diversi.

Tuttavia, la matematica afferma che ci sono già infiniti decimali tra 0 e 1. IEE 754 definisce una codifica per utilizzare questi 64 bit in modo efficiente per uno spazio numerico molto più ampio più NaN e +/- Infinity, quindi ci sono spazi vuoti tra numeri rappresentati accuratamente riempiti con numeri solo approssimativi.

Sfortunatamente lo 0,3 si trova in una lacuna.


4

Immagina di lavorare nella base dieci con, diciamo, 8 cifre di precisione. Tu controlli se

1/3 + 2 / 3 == 1

e scopri che questo ritorna false. Perché? Bene, come numeri reali abbiamo

1/3 = 0.333 .... e 2/3 = 0.666 ....

Troncando con otto cifre decimali, otteniamo

0.33333333 + 0.66666666 = 0.99999999

che è, ovviamente, diverso 1.00000000da esattamente 0.00000001.


La situazione per i numeri binari con un numero fisso di bit è esattamente analoga. Come numeri reali, abbiamo

1/10 = 0.0001100110011001100 ... (base 2)

e

1/5 = 0,0011001100110011001 ... (base 2)

Se li troncassimo a, diciamo, a sette bit, otterremmo

0.0001100 + 0.0011001 = 0.0100101

mentre d'altra parte,

3/10 = 0.01001100110011 ... (base 2)

che, troncato a sette bit, è 0.0100110e questi differiscono esattamente 0.0000001.


La situazione esatta è leggermente più sottile perché questi numeri sono in genere memorizzati in notazione scientifica. Quindi, ad esempio, invece di memorizzare 1/10 in quanto 0.0001100potremmo memorizzarlo come qualcosa di simile 1.10011 * 2^-4, a seconda di quanti bit abbiamo allocato per l'esponente e la mantissa. Ciò influisce sul numero di cifre di precisione ottenute per i calcoli.

Il risultato è che, a causa di questi errori di arrotondamento, in pratica non si vuole mai usare == su numeri in virgola mobile. Invece, puoi verificare se il valore assoluto della loro differenza è minore di un piccolo numero fisso.


4

Da Python 3.5 puoi usare la math.isclose()funzione per testare l'uguaglianza approssimativa:

>>> import math
>>> math.isclose(0.1 + 0.2, 0.3)
True
>>> 0.1 + 0.2 == 0.3
False

3

Poiché questa discussione si è ramificata un po 'in una discussione generale sulle attuali implementazioni in virgola mobile, aggiungerei che ci sono progetti per risolvere i loro problemi.

Dai un'occhiata a https://posithub.org/ per esempio, che mostra un tipo di numero chiamato posit (e il suo unum precedente) che promette di offrire una migliore precisione con meno bit. Se la mia comprensione è corretta, risolve anche il tipo di problemi nella domanda. Un progetto abbastanza interessante, la persona dietro di esso è un matematico, Dr. John Gustafson . Il tutto è open source, con molte implementazioni attuali in C / C ++, Python, Julia e C # ( https://hastlayer.com/arithmetics ).


3

In realtà è piuttosto semplice. Quando hai un sistema base 10 (come il nostro), può solo esprimere frazioni che usano un fattore primo della base. I fattori primi di 10 sono 2 e 5. Quindi 1/2, 1/4, 1/5, 1/8 e 1/10 possono tutti essere espressi in modo chiaro perché tutti i denominatori usano fattori primi di 10. Al contrario, 1 / 3, 1/6 e 1/7 sono tutti decimali ripetuti perché i loro denominatori usano un fattore primo di 3 o 7. In binario (o base 2), l'unico fattore primo è 2. Quindi puoi esprimere solo le frazioni in modo pulito che contiene solo 2 come fattore primo. In binario, 1/2, 1/4, 1/8 sarebbero tutti espressi in modo pulito come decimali. Mentre 1/5 o 1/10 ripetono i decimali. Quindi 0.1 e 0.2 (1/10 e 1/5) mentre i decimali puliti in un sistema di base 10, stanno ripetendo i decimali nel sistema di base 2 in cui il computer sta operando. Quando fai matematica su questi decimali ripetuti,

Da https://0.30000000000000004.com/


3

Numeri decimali come 0.1, 0.2e, 0.3non sono rappresentati esattamente in tipi binari in virgola mobile codificati. La somma delle approssimazioni per 0.1e 0.2differisce dall'approssimazione utilizzata per 0.3, quindi la falsità di 0.1 + 0.2 == 0.3come può essere vista più chiaramente qui:

#include <stdio.h>

int main() {
    printf("0.1 + 0.2 == 0.3 is %s\n", 0.1 + 0.2 == 0.3 ? "true" : "false");
    printf("0.1 is %.23f\n", 0.1);
    printf("0.2 is %.23f\n", 0.2);
    printf("0.1 + 0.2 is %.23f\n", 0.1 + 0.2);
    printf("0.3 is %.23f\n", 0.3);
    printf("0.3 - (0.1 + 0.2) is %g\n", 0.3 - (0.1 + 0.2));
    return 0;
}

Produzione:

0.1 + 0.2 == 0.3 is false
0.1 is 0.10000000000000000555112
0.2 is 0.20000000000000001110223
0.1 + 0.2 is 0.30000000000000004440892
0.3 is 0.29999999999999998889777
0.3 - (0.1 + 0.2) is -5.55112e-17

Affinché questi calcoli siano valutati in modo più affidabile, è necessario utilizzare una rappresentazione basata su decimali per i valori in virgola mobile. Lo standard C non specifica tali tipi per impostazione predefinita ma come estensione descritta in un rapporto tecnico .

I _Decimal32, _Decimal64e le _Decimal128tipologie potrebbero essere disponibili sul vostro sistema (per esempio, GCC li sostiene sul target selezionati , ma Clang non sono supportati su OS X ).


1

Math.sum (javascript) .... tipo di sostituzione dell'operatore

.1 + .0001 + -.1 --> 0.00010000000000000286
Math.sum(.1 , .0001, -.1) --> 0.0001

Object.defineProperties(Math, {
    sign: {
        value: function (x) {
            return x ? x < 0 ? -1 : 1 : 0;
            }
        },
    precision: {
        value: function (value, precision, type) {
            var v = parseFloat(value), 
                p = Math.max(precision, 0) || 0, 
                t = type || 'round';
            return (Math[t](v * Math.pow(10, p)) / Math.pow(10, p)).toFixed(p);
        }
    },
    scientific_to_num: {  // this is from https://gist.github.com/jiggzson
        value: function (num) {
            //if the number is in scientific notation remove it
            if (/e/i.test(num)) {
                var zero = '0',
                        parts = String(num).toLowerCase().split('e'), //split into coeff and exponent
                        e = parts.pop(), //store the exponential part
                        l = Math.abs(e), //get the number of zeros
                        sign = e / l,
                        coeff_array = parts[0].split('.');
                if (sign === -1) {
                    num = zero + '.' + new Array(l).join(zero) + coeff_array.join('');
                } else {
                    var dec = coeff_array[1];
                    if (dec)
                        l = l - dec.length;
                    num = coeff_array.join('') + new Array(l + 1).join(zero);
                }
            }
            return num;
         }
     }
    get_precision: {
        value: function (number) {
            var arr = Math.scientific_to_num((number + "")).split(".");
            return arr[1] ? arr[1].length : 0;
        }
    },
    sum: {
        value: function () {
            var prec = 0, sum = 0;
            for (var i = 0; i < arguments.length; i++) {
                prec = this.max(prec, this.get_precision(arguments[i]));
                sum += +arguments[i]; // force float to convert strings to number
            }
            return Math.precision(sum, prec);
        }
    }
});

l'idea è di usare la matematica invece degli operatori per evitare errori float

Math.sum rileva automaticamente la precisione da utilizzare

Math.sum accetta un numero qualsiasi di argomenti


1
Non sono sicuro che tu abbia risposto alla domanda " Perché accadono queste inesattezze? "
Wai Ha Lee,

in un certo senso hai ragione ma sono venuto qui da uno strano comportamento javascript riguardo questo problema ... voglio solo condividere il tipo di soluzione
bortunac,

Tuttavia non stai ancora rispondendo alla domanda.
Wai Ha Lee,

k hai un problema con questo ... dimmi dove spostarlo o se insisti posso semplicemente cancellarlo
bortunac

0

Ho appena visto questo interessante problema sui punti mobili:

Considera i seguenti risultati:

error = (2**53+1) - int(float(2**53+1))
>>> (2**53+1) - int(float(2**53+1))
1

Possiamo chiaramente vedere un punto di interruzione quando 2**53+1- tutto funziona bene fino a quando 2**53.

>>> (2**53) - int(float(2**53))
0

Inserisci qui la descrizione dell'immagine

Ciò accade a causa del binario a doppia precisione: IEEE 754 formato binario a virgola mobile a doppia precisione: binary64

Dalla pagina Wikipedia per il formato a virgola mobile a precisione doppia :

Il virgola mobile binaria a doppia precisione è un formato comunemente usato sui PC, grazie alla sua gamma più ampia rispetto alla virgola mobile a precisione singola, nonostante le sue prestazioni e il costo della larghezza di banda. Come nel formato a virgola mobile a precisione singola, manca di precisione sui numeri interi se confrontato con un formato intero della stessa dimensione. È comunemente noto semplicemente come doppio. Lo standard IEEE 754 specifica un binary64 come:

  • Bit di segno: 1 bit
  • Esponente: 11 bit
  • Precisione significativa: 53 bit (52 memorizzati in modo esplicito)

Inserisci qui la descrizione dell'immagine

Il valore reale assunto da un dato riferimento a doppia precisione a 64 bit con un dato esponente distorto e una frazione di 52 bit è

Inserisci qui la descrizione dell'immagine

o

Inserisci qui la descrizione dell'immagine

Grazie a @a_guest per avermelo segnalato.


-1

Una domanda diversa è stata nominata come duplicata di questa:

In C ++, perché il risultato è cout << xdiverso dal valore mostrato da un debugger x?

L' xnella questione è una floatvariabile.

Un esempio sarebbe

float x = 9.9F;

Il debugger mostra 9.89999962, l'output coutdell'operazione è 9.9.

La risposta risulta essere coutla precisione predefinita per float6, quindi arrotonda a 6 cifre decimali.

Vedi qui per riferimento


1
IMO - pubblicarlo qui è stato l'approccio sbagliato. So che è frustrante, ma le persone che hanno bisogno di una risposta alla domanda originale (apparentemente ora cancellata!) Non la troveranno qui. Se ritieni davvero che il tuo lavoro meriti di essere salvato, suggerirei: 1) cercare un'altra Q a cui effettivamente risponde, 2) creare una domanda con risposta autonoma.
Stephen C
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.