Float Precision, risolvere il problema dei decimali in JavaScript

4 febbraio 2009 at 21:51 11 commenti

Facendo operaizoni coi decimali in JavaScript vi sarà capitato di imbattervi in alcuni risultati “strani”… Ad esempio:

0.99999 + 0.000001 = 0.9999910000000001
0.05 * 0.35 = 0.017499999999999998

Questo strano comportamento è colpa dello Standard IEEE 754 implementato in moltissimi linguaggi e che regola il comportamento dei numeri decimali.
Ora, non so per voi ma per me è inconcepibile fare con un PC delle operazioni matematiche ed ottenere risultati errati… per cui mi sono messo a caccia di una libreria che risolvesse tale problema. Ho trovato BigNumber, che al prezzo di un po’ di scomodità (ma neanche tanta) promette risultati perfetti:

new BigNumber(0.99999).add(0.000001) = 0.999991
new BigNumber(0.05).multiply(0.35) = 0.0175

Alleluia.

Quando la finiremo di implementare librerie per riparare agli errori degli altri??

Entry filed under: JavaScript. Tags: .

jLibrary, minor update Gif animate con Photoshop

11 commenti Add your own

  • 1. Yota_VGA  |  8 maggio 2009 alle 01:02

    Sono capitato per caso nella tua pagina cercando delle informazioni…
    Ma sono rimasto allibito di quanto hai scritto, tanto da non riuscire a contenere un commento…

    Ma tu lo sai cosa stai scrivendo? Lo sai che certi numeri non puoi salvarli in alcun modo, e non c’è bignumber che tenga, se li salvi in qualunque base, anche se consideri soltanto i razionali?

    Lo sai quante risorse usa bignumber? Lo sai che prestazionalmente è decisamente lento se non fai solo semplici somme?

    Ma io dico, ormai sono tutti scienziati, tutti informatici espertissimi, e poi non hanno neanche la cognizione di quello che dicono. Io non riesco neanche a trovare la forza di dire qualcosa, a parte che vista la situazione mi sa che servirebbe un trattato per riuscire a spiegarti dove sta il problema.

    Al di la del fatto che sia discutibile l’implementazione di una libreria a precisione arbitraria in javascript come default, molto discutibile, proprio proprio in un linguaggio come questo se ne potrebbe pure parlare, ma le castronerie che si evincono da quello che dici sembrano essere davvero profonde…

    Scusa, non ce l’ho con te, è che sono veramebte allibito. Post scandalosi ne vedo spesso, ma questo è da guiseness… Poi magari è l’eccezione che conferma la regola, io proprio non ti conosco, ma sono rimasto davvero stupito a leggere una cosa simile…

    Senza rancore.

    Rispondi
  • 2. yemmi  |  8 maggio 2009 alle 09:19

    ma infatti non si tratta di un errore di altri da riparare :|

    semplicemente usare numeri di quel tipo costa molte risorse e francamente non ne vedo l’utilità in js
    neanche fosse un linguaggio per il alcolo scentifico

    Rispondi
  • 3. Yota_VGA  |  8 maggio 2009 alle 15:30

    Mi han fatto notare che ho scritto guiseness invece di guinness, pardon.

    Comunque volevo aggiungere (dopo uno studio un po’ più accurato della bignumber) che come pensavo è solo una libreria a precisione arbitraria. Guarda caso usa proprio lo standard che citi.

    Prova a rappresentare 1 / 10, e vedrai che non è rappresentabile in binario, qualunque sia la precisione che scegli.

    In ogni caso ti chiedo scusa per la sfuriata, ho esagerato credo un po’.

    Rispondi
  • 4. jure  |  15 maggio 2009 alle 12:12

    tranquillo, nessun problema per la sfuriata, ci mancherebbe altro che non ci si potesse esprimere :)

    Semplicemente BigNumber mi ha risolto un problema quando mi son trovato a lavorare con un’applicazione javascript che lavorava su piano cartesiano, l’ho usata ed ha funzionato. Non lo uso di default in tutte le mie applicazioni!

    Concordo su tutto quello che dite, prestazione, binario, eccetera.

    Ma ci sono certi casi in cui le prestazioni non sono importanti, come un programma ad uso didattico che data un’equazione disegna una retta.

    Conosco anche i limiti del binario, ma non vuol dire che accetti il fatto che nel 2009 un PC che fa un’operazione mi dia risultatierrati. Non mi pare che una normale calcolatrice da taschino abbia di questi problemi.

    Diversi cervelloni hanno lavorato a questo problema per tirar fuori una soluzione. Lo standard IEEE 754 è stato revisionato e pubblicato nell’Agosto del 2008, grazie al cielo. Chissà quando lo implementeranno nei motori JavaScript dei browser. Per il momento mi accontento di usare BigNumber quelle rare volte che serve.

    Rispondi
  • 5. Yota_VGA  |  15 maggio 2009 alle 17:52

    Le calcolatrici da taschino non hanno di questi problemi (o per lo meno molte) perché hanno circuiti che fanno calcoli in decimale, non in binario.

    Anche nelle calcolatrici da taschino, se fai 1 / 3, compare 0.333333… Non sono in grado di capire che la cifra non è rappresentabile in decimale, e se convertissi in un’altra base avresti lo stesso problema. Ed infatti se rimoltiplichi per 3 a seconda di come viene gestita l’approssimazione alcune calcolatrici potrebbero mostrare 0.9999999… invece di 1.
    Così come hai problemi se facessi operazioni che producono non numeri razionali, ma irrazionali, come alcune radici, seno e coseno, e molti altri problemi del tipo. Il fatto che l’uomo non veda molto facilmente questi errori, non vuol dire che non ci siano.

    Anche con bignumber comunque si hanno simili problemi. Semplicemente scegli la precisione a tuo piacimento, quindi puoi ridurne un gran numero a piacere.

    Esistono delle scappatoie da questi problemi, ad esempio se si lavora con solo numeri razionali è possibile salvare 2 interi (molto grandi) e lavorare sempre con questi, prendendoli come numeratore e denominatore dello stesso numero, questa in particolare è un’operazione che viene fatta da diverse applicazioni matematiche, come ad esempio matlab. Ma non è una cosa riproponibile per normali calcoli, sia per tempo che per spazio consumato.

    BigNumber, che già è una soluzione dispendiosa, non è la soluzione a questi problemi, è solo un’attenuazione di questi ultimi. Che per molte applicazioni può andare bene, ma non vuol dire che sia corretto. Usa gli stessi standard che usa l’hardware, con la differenza che usa mantissa ed esponente più grandi. Solo questo.

    Inoltre l’IEEE 754 definiva già i double-precision e gli extended-precision, ed il nuovo l’IEEE 754, pur davvero valido, non fa molto di nuovo in effetti in proposito, pur definendo finalmente una dimensione standard (sostituendo l’extended-precision che dava della precisione minima), il binary128, che diventa utile per scopi scientifici.

    Rispondi
    • 6. jure  |  20 maggio 2009 alle 16:17

      Il binario avrà i suoi problemi, ma la calcolatrice di Windows funziona benissimo. Concordo che BigNumber non è la soluzione ottimale, ma diamine, se una soluzione esiste (vedi calcolatrice di win) cosa aspettano ad implementarla?

      Rispondi
  • 7. Daniele  |  16 settembre 2009 alle 08:45

    Basta cercare un pò su google per capire che javascript ha sempre avuto problemi con i calcoli con i decimali..

    Ma al di là dell’IEEE 754, il problema maggiore rimane il browser: quello che io non riesco a concepire, e spesso fà perdere tempo e pazienza, è che lo stesso codice, la stessa operazione, abbia risultati diversi su firefox, chrome, safari, opera, e lo stramaledettissimo ie.

    Rispondi
  • 8. Marco  |  20 novembre 2009 alle 00:36

    Sono alle prese con il fatto che uno per 10 alla nona in js mi restituisce 999999999.9999999 e non riesco a venirne a capo… l’errore è inevitabile o ci sono delle scappatoie?

    Rispondi
  • 9. jure  |  17 dicembre 2009 alle 10:31

    ho fatto una prova con l’ultima versione di explorer,firefox,safari,opera e chrome, ma il risultato è sempre giusto:

    10*10*10*10*10*10*10*10*10 =1000000000
    Math.pow(10,9) =1000000000
    new BigNumber(10).pow(9) =1000000000

    che browser/versione usi?

    Rispondi
  • 10. Antonio  |  8 settembre 2012 alle 10:38

    Scusate se riapro un post del 2009, ma visto che compare tra i primi risultati con Google, mi permetto di apportare un’aggiunta forse utile a qualcuno per risolvere velocemente.
    Con la funzione toFixed, dove preferibile, si può arrotondare il risultato:
    http://www.w3schools.com/jsref/jsref_tofixed.asp

    Rispondi
    • 11. jure  |  8 settembre 2012 alle 19:35

      Ciao Antonio, grazie per il contributo! Purtroppo però anche toFixed() e toPrecision() sono soggetti a errori:

      (162.295).toFixed(2) = “162.29” invece di “162.30”
      (162.295).toPrecision(5) = “162.29” invece di “162.30”

      Non ci si può proprio fidare!
      Di BigNumber invece sì, è una libreria che può aiutare a completare più velocemente certi lavori, ma in caso abbiate bisogno di prestazioni vi consiglio di:
      – lavorare con i numeri interi (cents invece di euro, millimetri invece di centimetri, ecc), convertire il risultato in stringa e aggiungere il punto con substr() vari
      – convertire i numeri in stringa, splittare il punto, calcolare separatamente interi e decimali e poi rimontare il tutto come stringa
      – creare array con risultati precalcolati in altri modi

      Rispondi

Lascia un commento

Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione / Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione / Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione / Modifica )

Google+ photo

Stai commentando usando il tuo account Google+. Chiudi sessione / Modifica )

Connessione a %s...

Trackback this post  |  Subscribe to the comments via RSS Feed


Categorie

JavaScript String .replace

Archivi

Seguimi su Twitter


%d blogger cliccano Mi Piace per questo: