web-dev-qa-db-fra.com

Nombres négatifs en chaîne binaire en JavaScript

Tout le monde sait pourquoi javascript Number.toString la fonction ne représente pas correctement les nombres négatifs?

//If you try
(-3).toString(2); //shows "-11"
// but if you fake a bit shift operation it works as expected
(-3 >>> 0).toString(2); // print "11111111111111111111111111111101"

Je suis vraiment curieux de savoir pourquoi cela ne fonctionne pas correctement ou quelle est la raison pour laquelle cela fonctionne de cette façon? Je l'ai cherché mais je n'ai rien trouvé qui puisse aider.

44
fernandosavio

Réponse courte:

  1. La fonction toString() prend la décimale, la convertit en binaire et ajoute un signe "-".

  2. Un décalage vers la droite de remplissage nul convertit ses opérandes en entiers 32 bits signés dans un format à deux compléments.

Une réponse plus détaillée:

Question 1:

//If you try
(-3).toString(2); //show "-11"

C'est dans la fonction .toString() . Lorsque vous sortez un nombre via .toString():

Syntaxe

numObj.toString ([radix])

Si numObj est négatif, le signe est conservé. C'est le cas même si le radix est 2; la chaîne renvoyée est la représentation binaire positive de numObj précédée d'un signe -, et non le complément à deux de numObj.

Il prend la décimale, la convertit en binaire et ajoute un signe "-".

  1. Base 10 "3" convertie en base 2 est "11"
  2. Ajouter un signe nous donne "-11"

Question 2:

// but if you fake a bit shift operation it works as expected
        (-3 >>> 0).toString(2); // print "11111111111111111111111111111101"

Un décalage vers la droite de remplissage nul convertit ses opérandes en entiers 32 bits signés . Le résultat de cette opération est toujours un entier 32 bits non signé.

Les opérandes de tous les opérateurs au niveau du bit sont convertis en entiers 32 bits signés au format complément à deux.

16
Daan

-3 >>> 0 (décalage logique vers la droite) contraint ses arguments à des entiers non signés, c'est pourquoi vous obtenez la représentation du complément à 32 bits de -3.

http://en.wikipedia.org/wiki/Two%27s_complement

http://en.wikipedia.org/wiki/Logical_shift

27
Steve Wang
var binary = (-3 >>> 0).toString(2); // coerced to uint32

console.log(binary);

console.log(parseInt(binary, 2) >> 0); // to int32

le jsfiddle

la sortie est

11111111111111111111111111111101
-3 
18
Xotic750

.toString() est conçu pour renvoyer le signe du nombre dans la représentation sous forme de chaîne. Voir EcmaScript 2015, section 7.1.12.1 :

  1. Si m est inférieur à zéro, renvoyez la concaténation String de la chaîne "-" et ToString (- m ).

Cette règle n'est pas différente quand un radix est passé en argument, comme on peut le conclure de section 20.1.3.6 :

  1. Renvoie la représentation String de cette valeur Number en utilisant le radix spécifié par radixNumber . [...] l'algorithme devrait être une généralisation de celui spécifié au 7.1.12.1.

Une fois que cela est compris, la chose la plus surprenante est de savoir pourquoi il n'en fait pas de même avec -3 >>> 0.

Mais ce comportement n'a en fait rien à voir avec .toString(2), car la valeur est déjà différente avant de l'appeler:

console.log (-3 >>> 0); // 4294967293

C'est la conséquence du comportement de l'opérateur >>>.

Cela n'aide pas non plus que (au moment de la rédaction) les informations sur mdn ne soient pas entièrement correctes. Ça dit:

Les opérandes de tous les opérateurs au niveau du bit sont convertis en entiers 32 bits signés au format complément à deux.

Mais ce n'est pas vrai pour tous les opérateurs au niveau du bit. L'opérateur >>> Est une exception à la règle. Cela ressort clairement du processus d'évaluation spécifié dans EcmaScript 2015, section 12.5.8.1 :

  1. Soit lnum ToUint32 ( lval ).

L'opération ToUint32 a une étape où l'opérande est mappé dans la plage 32 bits non signée:

  1. Soit int32bit soit int modulo 232.

Lorsque vous appliquez l'opération modulo mentionnée ci-dessus (à ne pas être confus avec l'opérateur % De JavaScript) à l'exemple de valeur -3, vous obtenez en effet 4294967293.

Comme -3 et 4294967293 ne sont évidemment pas le même nombre, il n'est pas surprenant que (-3).toString(2) Ne soit pas le même que (4294967293).toString(2).

1
trincot

Juste pour résumer quelques points ici, si les autres réponses sont un peu déroutantes:

  • ce que nous voulons obtenir, c'est la représentation sous forme de chaîne d'un nombre négatif en représentation binaire; cela signifie que la chaîne doit afficher un nombre binaire signé (en utilisant le complément à 2)
  • l'expression (-3 >>> 0).toString(2), appelons-la A, fait le travail; mais nous voulons savoir pourquoi et comment cela fonctionne
  • si nous avions utilisé var num = -3; num.toString(-3) nous aurions obtenu -11, qui est simplement la représentation binaire non signée du nombre 3 avec un signe négatif devant, ce qui n'est pas ce que nous voulons
  • l'expression A fonctionne comme ceci:

1) (-3 >>> 0)

L'opération >>> Prend l'opérande gauche (-3), qui est un entier signé, et décale simplement les positions des bits 0 vers la gauche (donc les bits sont inchangés), et le nombre non signé correspondant à ces bits inchangés .

La séquence de bits du numéro signé -3 est la même séquence de bits que le numéro non signé 4294967293, qui est ce que le nœud nous donne si nous tapons simplement -3 >>> 0 Dans le REPL.

2) (-3 >>> 0).toString

Maintenant, si nous appelons toString sur ce nombre non signé, nous obtiendrons simplement la représentation sous forme de chaîne des bits du nombre, qui est la même séquence de bits que -3.

Ce que nous avons effectivement fait était de dire "hé toString, vous avez un comportement normal quand je vous dis d'imprimer les bits d'un entier non signé, donc comme je veux imprimer un entier signé, je vais juste le convertir en un entier non signé, et vous imprimez les morceaux pour moi. "

0
evianpring