web-dev-qa-db-fra.com

Grands nombres arrondis par erreur en JavaScript

Voir ce code:

<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>

Lorsque je vois ma console dans Firefox 3.5, la valeur de jsonParsed est:

Object id=714341252076979100 type=FUZZY

C'est-à-dire que le nombre est arrondi. J'ai essayé différentes valeurs, le même résultat (nombre arrondi).

Je n'ai pas non plus ses règles d'arrondi. 714341252076979136 est arrondi à 714341252076979200, tandis que 714341252076979135 est arrondi à 714341252076979100.

EDIT: Voir le premier commentaire ci-dessous. Apparemment, il ne s'agit pas de JSON, mais de la gestion des numéros JavaScript. Mais la question demeure:

Pourquoi cela arrive-t-il?

54
Jaanus

Ce que vous voyez ici est en fait l'effet de deux arrondis. Les nombres dans ECMAScript sont des virgules flottantes double précision représentées en interne. Lorsque id est défini sur 714341252076979033 (0x9e9d9958274c359 En hexadécimal), la valeur de double précision représentable la plus proche lui est attribuée, qui est 714341252076979072 (0x9e9d9958274c380). Lorsque vous imprimez la valeur, elle est arrondie à 15 chiffres décimaux significatifs, ce qui donne 14341252076979100.

61
Stephen Canon

Vous débordez de la capacité du type de numéro JavaScript, voir §8.5 de la spécification pour plus de détails. Ces identifiants devront être des chaînes.

La virgule flottante double précision IEEE-754 (le type de nombre utilisé par JavaScript) ne peut pas représenter avec précision tous nombres (bien sûr). Communément, 0.1 + 0.2 == 0.3 c'est faux. Cela peut affecter des nombres entiers comme il affecte des nombres fractionnaires; il commence une fois que vous avez dépassé 9 007 199 254 740 991 (Number.MAX_SAFE_INTEGER).

Au-delà Number.MAX_SAFE_INTEGER + 1 (9007199254740992), le format à virgule flottante IEEE-754 ne peut plus représenter chaque entier consécutif. 9007199254740991 + 1 est 9007199254740992, mais 9007199254740992 + 1 est également 9007199254740992 car 9007199254740993 ne peut pas être représenté dans le format. Le suivant qui peut être est 9007199254740994. Ensuite 9007199254740995 ne peut pas l'être, mais 9007199254740996 pouvez.

La raison en est que nous n'avons plus de bits, donc nous n'avons plus de bit 1s; le bit de poids faible représente maintenant des multiples de 2. Finalement, si nous continuons, nous perdons ce bit et ne travaillons que par multiples de 4. Et ainsi de suite.

Vos valeurs sont bien au-dessus de ce seuil, et elles sont donc arrondies à la valeur représentable la plus proche.


Si vous êtes curieux de connaître les bits, voici ce qui se passe: Un nombre à virgule flottante double précision IEEE-754 a un bit de signe, 11 bits d'exposant (qui définit l'échelle globale du nombre, comme une puissance de 2 [ parce que c'est un format binaire]), et 52 bits de signification (mais le format est tellement intelligent qu'il obtient 53 bits de précision sur ces 52 bits). La façon dont l'exposant est utilisé est compliquée ( décrite ici ), mais en termes très vagues, si nous en ajoutons un à l'exposant , la valeur de la significande est doublée, puisque l'exposant est utilisé pour des puissances de 2 (encore une fois, faites attention, ce n'est pas direct, il y a de l'intelligence là-dedans).

Regardons donc la valeur 9007199254740991 (alias, Number.MAX_SAFE_INTEGER):

 + −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− −−−−−−−−−−−−−−−−−−− bit de signe 
/+ −−−−−−− + −−−−−−−−−−−−−−− −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− exposant 
// | + −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− + - signification 
// |/| 
 0 10000110011 1111111111111111111111111111111111111111111111111111 
 = 9007199254740991 (Number.MAX_SAFE_INTEGER) 

Cette valeur d'exposant, 10000110011, signifie que chaque fois que nous ajoutons un à la signification, le nombre représenté augmente de 1 (le nombre entier 1, nous avons perdu la capacité de représenter des nombres fractionnaires beaucoup plus tôt).

Mais maintenant, cette signification est pleine. Pour dépasser ce nombre, nous devons augmenter l'exposant, ce qui signifie que si nous en ajoutons un à la significande, la valeur du nombre représenté augmente de 2, pas de 1 (car l'exposant est appliqué à 2, la base de ce nombre à virgule flottante binaire):

 + −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− −−−−−−−−−−−−−−−−−−− bit de signe 
/+ −−−−−−− + −−−−−−−−−−−−−−− −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− exposant 
// | + −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− + - signification 
// |/| 
 0 10000110100 0000000000000000000000000000000000000000000000000000 
 = 9007199254740992 (Number.MAX_SAFE_INTEGER + 1) 

Eh bien, ça va, parce que 9007199254740991 + 1 est 9007199254740992 en tous cas. Mais! Nous ne pouvons pas représenter 9007199254740993. Nous avons manqué de bits. Si nous ajoutons seulement 1 à la signification, cela ajoute 2 à la valeur:

 + −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− −−−−−−−−−−−−−−−−−−− bit de signe 
/+ −−−−−−− + −−−−−−−−−−−−−−− −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− exposant 
// | + −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− + - signification 
// |/| 
 0 10000110100 0000000000000000000000000000000000000000000000000001 
 = 9007199254740994 (Number.MAX_SAFE_INTEGER + 3) 

Le format ne peut plus représenter des nombres impairs car nous augmentons la valeur, l'exposant est trop grand.

Finalement, nous manquons à nouveau de bits de signification et devons augmenter l'exposant, donc nous finissons par ne pouvoir représenter que des multiples de 4. Puis des multiples de 8. Puis des multiples de 16. Et ainsi de suite.

51
T.J. Crowder

Il n'est pas causé par cet analyseur json. Essayez simplement d'entrer 714341252076979033 sur la console de fbug. Vous verrez le même 714341252076979100.

Voir cet article de blog pour plus de détails: http://www.exploringbinary.com/print-precision-of-floating-point-integers-varies-too

9
thorn̈

JavaScript utilise des valeurs à virgule flottante double précision, soit une précision totale de 53 bits, mais vous avez besoin

ceil(lb 714341252076979033) = 60

bits pour représenter exactement la valeur.

Le nombre exactement représentable le plus proche est 714341252076979072 (Écrivez le numéro d'origine en binaire, remplacez les 7 derniers chiffres par 0 Et arrondissez car le chiffre remplacé le plus élevé était 1).

Vous obtiendrez 714341252076979100 À la place de ce nombre car ToString() comme décrit par ECMA-262, §9.8.1 fonctionne avec des puissances de dix et avec une précision de 53 bits tous ces nombres sont égaux.

5
Christoph

Le problème est que votre numéro nécessite une plus grande précision que JavaScript.

Pouvez-vous envoyer le numéro sous forme de chaîne? Séparé en deux parties?

4
Esteban Küber

JavaScript ne peut gérer que des nombres entiers exacts jusqu'à environ 9000 millions de millions (soit 9 avec 15 zéros). Plus haut que cela et vous obtenez des ordures. Contournez cela en utilisant des chaînes pour contenir les nombres. Si vous avez besoin de faire des calculs avec ces nombres, écrivez vos propres fonctions ou voyez si vous pouvez leur trouver une bibliothèque: je suggère les premières car je n'aime pas les bibliothèques que j'ai vues. Pour commencer, voyez deux de mes fonctions à ne autre réponse .

2
Robert L