javascript
  • floating-point
  • floating-accuracy
  • ieee-754
  • 2009-09-04 11 views 38 likes 
    38

    Vedi questo codice:Grandi numeri erroneamente arrotondati in Javascript

    <html> 
    <head> 
    
    <script src="http://www.json.org/json2.js" type="text/javascript"></script> 
    
    <script type="text/javascript"> 
    
        var jsonString = '{"id":714341252076979033,"type":"FUZZY"}'; 
        var jsonParsed = JSON.parse(jsonString); 
        console.log(jsonString, jsonParsed); 
    
    
    </script> 
    </head> 
    <body> 
    </body> 
    </html> 
    

    Quando vedo la mia console in Firefox 3.5, il valore di jsonParsed è:

    Object id=714341252076979100 type=FUZZY 
    

    cioè il numero viene arrotondato. Provato diversi valori, lo stesso risultato (numero arrotondato).

    Inoltre non ottengo le sue regole di arrotondamento. 714341252076979136 viene arrotondato a 714341252076979200, mentre 714341252076979135 viene arrotondato a 714341252076979100.

    MODIFICA: vedere il primo commento qui sotto. Apparentemente non si tratta di JSON, ma qualcosa sulla gestione dei numeri Javascript. Ma la domanda rimane:

    Perché sta succedendo?

    +0

    Grazie a tutti per le risposte rapide e utili, vorrei poter contrassegnare tutte e 3 le risposte ufficiali. – Jaanus

    risposta

    47

    Quello che vedete qui è in realtà l'effetto di due arrotondamenti. I numeri in ECMAScript sono rappresentati in virgola mobile a precisione doppia. Quando id è impostato su 714341252076979033 (0x9e9d9958274c359 in esadecimale), in realtà viene assegnato il valore rappresentativo a precisione doppia più vicino, ovvero 714341252076979072 (0x9e9d9958274c380). Quando si stampa il valore, viene arrotondato a 15 cifre decimali significative, il che dà 14341252076979100.

    7

    Non è causato da questo parser json. Prova a inserire 714341252076979033 nella console di fbug. Vedrete la stessa 714341252076979100.

    Vedi questo post del blog per i dettagli: http://www.exploringbinary.com/print-precision-of-floating-point-integers-varies-too

    +6

    Grazie per il collegamento al mio articolo, ma spiega solo metà del problema: la STAMPA del valore arrotondato internamente. Anche se javascript ti permette di stampare tutto, sarebbe ancora sbagliato - sarebbe il valore rappresentativo a doppia precisione più vicino, come spiegato da altri sotto. –

    40

    sei traboccante la capacità di JavaScript tipo di numero, vedere §8.5 of the spec per i dettagli. Questi ID dovranno essere stringhe.

    IEEE-754 virgola mobile a precisione doppia (il tipo di numero utilizzato da JavaScript) non può rappresentare precisamente tutti i numeri (ovviamente). Notoriamente, 0.1 + 0.2 == 0.3 è falso. Questo può influenzare numeri interi proprio come influenza numeri frazionari; inizia una volta superato 9,007,199,254,740,991 (Number.MAX_SAFE_INTEGER).

    Oltre Number.MAX_SAFE_INTEGER + 1 (9007199254740992), il formato a virgola mobile IEEE-754 non può più rappresentare ogni intero consecutivo. 9007199254740991 + 1 è 9007199254740992, ma 9007199254740992 + 1 è anche9007199254740992 perché 9007199254740993 non può essere rappresentato nel formato. Il prossimo che può essere è 9007199254740994. Quindi non può essere 9007199254740995, ma 9007199254740996 can.

    Il motivo è che abbiamo esaurito i bit, quindi non abbiamo più un bit da 1s; il bit di ordine inferiore ora rappresenta multipli di 2. Alla fine, se continuiamo, perdiamo quel bit e funzionano solo in multipli di 4. E così via.

    I valori sono pozzetto superiori a tale soglia e pertanto vengono arrotondati al valore rappresentativo più vicino.


    Se siete curiosi di sapere i bit, ecco cosa succede: un binario numero a virgola mobile a doppia precisione IEEE-754 ha un bit di segno, 11 bit di esponente (che definisce la dimensione complessiva del numero , come potenza di 2 [perché questo è un formato binario]), e 52 bit di significato e (ma il formato è così intelligente ottiene 53 bit di precisione da quei 52 bit). Il modo in cui viene utilizzato l'esponente è complicato (described here), ma in molto termini vaghi, se ne aggiungiamo uno all'esponente, il valore del significato è raddoppiato, poiché l'esponente è usato per le potenze di 2 (di nuovo, avvertenza, non è diretto, c'è intelligenza lì dentro).

    Quindi diamo un'occhiata al valore 9007199254740991 (aka, Number.MAX_SAFE_INTEGER):

     
       +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− sign bit 
      / +−−−−−−−+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− exponent 
     //  | +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+− significand 
    //   |/            | 
    0 10000110011 1111111111111111111111111111111111111111111111111111 
                    = 9007199254740991 (Number.MAX_SAFE_INTEGER) 
    

    Tale valore esponente, 10000110011, significa che ogni volta che aggiungiamo una al significante, il numero rappresentato sale di 1 (il intero numero 1, abbiamo perso la capacità di rappresentare numeri frazionari molto prima).

    Ma ora questo significato è pieno. Per superare quel numero, dobbiamo aumentare l'esponente, il che significa che se ne aggiungiamo uno al significando, il valore del numero rappresentato sale di 2, non di 1 (perché l'esponente è applicato a 2, la base di questo numero binario in virgola mobile):

     
       +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− sign bit 
      / +−−−−−−−+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− exponent 
     //  | +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+− significand 
    //   |/            | 
    0 10000110100 0000000000000000000000000000000000000000000000000000 
                    = 9007199254740992 (Number.MAX_SAFE_INTEGER + 1) 
    

    Beh, va bene, perché è 9007199254740991 + 19007199254740992 comunque. Ma! Non possiamo rappresentare 9007199254740993. Abbiamo finito i bit. Se aggiungiamo solo 1 per il significante, si aggiunge 2 al valore:

     
       +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− sign bit 
      / +−−−−−−−+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− exponent 
     //  | +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+− significand 
    //   |/            | 
    0 10000110100 0000000000000000000000000000000000000000000000000001 
                    = 9007199254740994 (Number.MAX_SAFE_INTEGER + 3) 
    

    Il formato solo non può rappresentare più numeri dispari, come si aumenta il valore, l'esponente è troppo grande.

    Alla fine, esauriamo nuovamente significato e bit e dobbiamo aumentare l'esponente, quindi finiamo per essere in grado di rappresentare solo multipli di 4. Quindi multipli di 8. Quindi multipli di 16. E così via.

    +4

    Mi piace questa risposta perché in realtà ti dice come risolvere il problema. – jsh

    2

    Il problema è che il numero richiede una precisione maggiore rispetto a JavaScript.

    È possibile inviare il numero come stringa? Separato in due parti?

    4

    JavaScript utilizza doppia precisione valori in virgola mobile, ossia una precisione di 53 bit, ma è necessario

    ceil(lb 714341252076979033) = 60 
    

    bit per rappresentare esattamente il valore.

    Il numero esattamente rappresentabile piu '714341252076979072 (scrivere il numero originale in binario, sostituire le ultime 7 cifre con 0 e arrotondare perché la cifra più alta sostituito era 1).

    Otterrete 714341252076979100 invece di questo numero perché ToString() come descritto da ECMA-262, §9.8.1 funziona con potenze di dieci e in precisione a 53 bit tutti questi numeri sono uguali.

    1

    JavaScript può gestire solo numeri interi esatti fino a circa 9000 milioni di milioni (ovvero 9 con 15 zeri). Più in alto e ottieni spazzatura. Lavorare su questo utilizzando le stringhe per contenere i numeri. Se hai bisogno di fare matematica con questi numeri, scrivi le tue funzioni o vedi se riesci a trovare una libreria per loro: ti suggerisco come non mi piacciono le librerie che ho visto. Per iniziare, vedere due delle mie funzioni allo another answer.

    Problemi correlati