4
votes

Nombre à virgule flottante en JavaScript (IEEE 754)

 entrez la description de l'image ici

Si je comprends bien, les nombres JavaScript sont toujours stockés sous forme de nombres à virgule flottante double précision, conformément à la norme internationale IEEE 754. Ce qui signifie qu'il utilise 52 bits pour la fraction significande. Mais dans l'image ci-dessus, il semble que 0,57 en binaire utilise 54 bits.

Une autre chose est (si je comprends bien) 0,55 en binaire est aussi un nombre répétitif. Mais pourquoi 0,55 + 1 = 1,55 (pas de perte) et 0,57 + 1 = 1,5699999999999998

 4


10 commentaires

Copie possible de Le calcul en virgule flottante est-il cassé?


Je suppose que .toString (2) arrondit pendant la conversion ...


Vous combattez les caprices des implémentations de bibliothèques JS Math dans différents navigateurs. De plus, l'architecture de la machine (matérielle) peut appliquer des normes différentes pour les représentations internes. Ajoutez la folie des implémentations mathématiques en virgule flottante sur différents systèmes d'exploitation, et vous n'avez presque aucun espoir de répondre à votre question en général. Quel processeur (cpu), quel système d'exploitation, quel navigateur ... ayant tout cela, vous POURREZ être en mesure de répondre à votre question pour cette combinaison spécifique.


@RichardUie Votre commentaire est aussi une réponse :)


@RichardUie: JavaScript implémente ECMA-262, et ECMA-262 spécifie le format Number et son arithmétique. Ces opérations dans cette question ne diffèrent pas entre les différentes implémentations correctes de JavaScript.


@phuzi: Non, ce n'est pas un doublon. Le comportement d'affichage demandé ici est dû à la spécification ECMA-262, et non à la virgule flottante en général.


@Eric 1) JavaScript n'est pas une implémentation - c'est une autre spécification; 2) les navigateurs se conforment dans la mesure où leurs auteurs le décident; 3) la conformité du navigateur n'impose aucune obligation absolue au système d'exploitation sous-jacent; 4) Les bibliothèques mathématiques du système d'exploitation ne peuvent pas maîtriser comme par magie les limitations architecturales physiques.


@RichardUie: 1) Le mot «implémentation» n'est pas limité à un programme ou une machine qui implémente une spécification. Il peut être utilisé pour décrire une chose abstraite, y compris une spécification d'un langage de programmation, qui se conforme à une autre chose. Dans tous les cas, la terminologie est sans importance. Le point est que JavaScript est conforme à ECMAScript. 2) J'ai dit que les implémentations correctes , c'est-à-dire conformes, ne diffèrent pas. 3) Ce n'est pas pertinent.


@RichardUie: 4) Les machines que nous utilisons sont, à des fins pratiques, des machines de Turing universelles. Le logiciel peut être conçu pour effectuer des calculs arbitraires, quelle que soit la facilité avec laquelle le matériel sous-jacent le rend. Il est certainement possible pour les logiciels mathématiques de fournir des fonctions différentes de celles fournies directement par le matériel. De plus, aucun des comportements interrogés dans la question ne s'écarte de ce qui est produit par une implémentation conforme à ECMAScript; les observations sont expliquées par ECMAScript.


@Eric 1) Les machines que nous utilisons sont, à des fins pratiques, des machines de Turing universelles. - utilisation inutile du jargon "Universal Turing Machine, car c'est un concept abstrait. 2) Le logiciel peut être conçu pour faire des calculs arbitraires quelle que soit la facilité avec laquelle le matériel sous-jacent le rend. - Le fait qu'il PEUT ne signifie pas qu'il DOIT ou FAIT ... aussi, "Turing Complete", l'utilisation la plus correcte, signifie peut faire tout ce qu'une autre machine peut faire NE RIEN faire. Étant donné votre parfaite compréhension de l'explicabilité de ces artefacts strictement en termes ECMA , où est votre explication?


3 Réponses :


1
votes

Number.prototype.toString implémente à peu près la section suivante de la spécification ES262:

7.1.12.1 NumberToString (m)

soit n, k et s des entiers tels que k ≥ 1, 10 ** k-1 ≤ s <10 ** k,

la valeur numérique pour s × 10 ** n-k est m,

et k est aussi petit que possible.

Par conséquent, toString estime simplement la valeur, il ne renvoie pas les octets exacts stockés.

Ce que vous voyez dans la console n'est pas non plus une représentation exacte.


9 commentaires

Merci, en fait je veux en savoir plus pourquoi 0.55 peut être présenté précisément quand 0.57 ne peut pas être: -?


@hungneox peut-il être représenté exactement? Comment sais-tu ça? Parce que ça ressemble à ça?


Non, en fait, je sais que 0,55 en binaire est également un nombre récurrent. Mais ma question ici pourquoi JS peut le gérer correctement.


@hungneox il ne peut pas. La console s'affiche comme si elle le ferait.


Vous vous méprenez probablement. Je ne comprends pas pourquoi il n'affiche pas 0,55 comme 0,54999998 comme "0,57"


@hungneox car toString () l'estime à 0,55


Rien à voir avec toString je suppose. 1 + 0,55 = 1,55


@hungneox la console affiche des chaînes (un groupe de caractères).


Je suppose que c'est un mécanisme d'arrondi que JS utilise, mais je suis heureux que quelqu'un puisse l'expliquer.



3
votes

toString (2) imprime la chaîne jusqu'au dernier chiffre différent de zéro.

1.57 a une représentation de bits différente de 1 + 0,57 (mais il n'est pas impossible d'obtenir le résultat 1.57),
mais 1 + 0,55 en binaire équivaut à 1,55 comme vous pouvez le voir dans l'extrait ci-dessous:

console.log(1.57)
console.log(1.57.toString(2))
console.log((1+.57).toString(2))
console.log("1.32 + 0.25 = ",1.32 + .25)
console.log((1.32 + .25).toString(2))
console.log(1.55)
console.log(1.55.toString(2))
console.log((1+.55).toString(2))

N'oubliez pas que l'ordinateur effectue des opérations sur les nombres binaires, 1.57 ou 1.55 est juste une sortie lisible par l'homme


0 commentaires

4
votes

Ce qui signifie qu'il utilise 52 bits pour la fraction significande. Mais dans l'image ci-dessus, il semble que 0,57 en binaire utilise 54 bits.

Le type Number de JavaScript, qui est essentiellement une virgule flottante binaire 64 bits de base IEEE 754, a des significands de 53 bits. 52 bits sont codés dans le champ «significand final». Le bit de tête est codé via le champ d'exposant (un champ d'exposant de 1 à 2046 signifie que le bit de tête est un, un champ d'exposant de 0 signifie que le bit de tête est zéro et un champ d'exposant de 2047 est utilisé pour l'infini ou NaN).

La valeur que vous voyez pour 0,57 a 53 bits significatifs. Le premier "0". est produit par l'opération toString ; il ne fait pas partie de l'encodage du nombre.

Mais pourquoi 0,55 + 1 = 1,55 (aucune perte) et 0,57 + 1 = 1,5699999999999998.

Lorsque JavaScript met en forme un Nombre x pour l'affichage avec ses règles par défaut, ces règles indiquent de produire le nombre décimal le plus court (dans ses chiffres significatifs, sans compter les décorations comme un «0» en tête) qui, une fois reconverti au format Number , produit x . Les objectifs de cette règle incluent (a) toujours s'assurer que l'affichage identifie de manière unique quelle valeur exacte de Number était la valeur source et (b) ne pas utiliser plus de chiffres que nécessaire pour accomplir (a).

Ainsi, si vous commencez par un nombre décimal tel que .57 et que vous le convertissez en Number , vous obtenez une valeur x qui est le résultat de la conversion ayant pour arrondir à un nombre représentable au format Number . Ensuite, lorsque x est formaté pour l'affichage, vous obtenez le numéro d'origine, car la règle qui dit de produire le nombre le plus court qui se reconvertit en x produit naturellement le nombre que vous avez commencé avec.

(Mais ce x ne représente pas exactement 0,57. Le double le plus proche à 0,57 est légèrement en dessous; voir les représentations décimales et binaires64 de celui-ci sur une double calculatrice IEEE).

D'un autre côté, lorsque vous effectuez une opération telle que .57 + 1 , vous faites de l'arithmétique qui produit un nombre y qui n'a pas commencé comme un chiffre décimal simple. Ainsi, lors du formatage d'un tel nombre pour l'affichage, la règle peut nécessiter l'utilisation de plus de chiffres. En d'autres termes. lorsque vous ajoutez .57 et 1 , le résultat au format Number n'est pas le même nombre que celui obtenu à partir de 1.57 . Ainsi, pour formater le résultat de .57 + 1 , JavaScript doit utiliser plus de chiffres pour distinguer ce nombre du nombre que vous obtenez de 1,57 - ils sont différents et doivent être affiché différemment.


Si 0,57 était exactement représentable comme un double , le résultat de pré-arrondi de la somme serait exactement 1,57, donc 1 + 0,57 arrondirait au même doubler comme 1.57 .

Mais ce n'est pas le cas, c'est en fait 1 + le plus proche_double (0.57) =
1.569999999999999951150186916493 (pré-arrondi, pas un double ) qui s'arrondit à
1.57000000000000006217248937901 , ce n'est donc pas une option pour imprimer le résultat de 1 + 0.57 . La chaîne décimale doit distinguer le nombre des valeurs binaires64 adjacentes.


Il se trouve que l'arrondi qui se produit dans .55 + 1 donne le même nombre que l'on obtient en convertissant 1,55 en Nombre , donc afficher le résultat de .55 + 1 produit "1,55".


3 commentaires

Je ne pense pas que vous ayez explicitement souligné que le double le plus proche de 0,57 ne représente pas exactement ce nombre; certaines personnes peuvent manquer le fait qu'imprimer en 0.57 ne signifie pas que c'est exactement 0.57 . C'est pourquoi 1 + 0.57 peut être un nombre différent de 1.57 ; si 0,57 pouvait être stocké exactement, la somme qui doit être arrondie au double le plus proche serait exactement 1,57 , identique au littéral décimal.


@PeterCordes: N'hésitez pas à modifier; Cela ne me dérangera pas. Je suis sur mobile jusqu'à demain après-midi, donc ajouter des exemples détaillés serait fastidieux. Je vais alors jeter un autre regard.


Terminé, ajout des représentations décimales de double (0,57) , 1 + double (0,57) (pré et post arrondi à double ), et `double (1,57).