61

任何人都知道为什么javascriptNumber.toString函数不能正确表示负数?

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

我真的很好奇为什么它不能正常工作或者它以这种方式工作的原因是什么?我已经搜索过它,但没有找到任何有帮助的东西。

4

6 回答 6

30

简短的回答:

  1. toString()函数采用十进制,将其转换为二进制并添加“-”符号。

  2. 零填充右移将其操作数转换为两个补码格式的有符号 32 位整数。

更详细的答案:

问题一:

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

它在函数中.toString()。当您通过以下方式输出数字时.toString()

句法

numObj.toString([基数])

如果 numObj 为负,则保留符号。即使基数为 2 也是如此;返回的字符串是 numObj 的正二进制表示,前面带有 - 号,而不是 numObj 的二进制补码。

它采用十进制,将其转换为二进制并添加“-”号。

  1. 以 10 为底的“3”转换为以 2 为底的为“11”
  2. 添加一个符号给我们“-11”

问题2:

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

零填充右移将其操作数转换为带符号的 32 位整数。该操作的结果始终是一个无符号的 32 位整数。

所有位运算符的操作数都转换为二进制补码格式的有符号 32 位整数。

于 2016-06-07T15:11:44.850 回答
29

-3 >>> 0(右逻辑移位)将其参数强制转换为无符号整数,这就是为什么您会得到 -3 的 32 位二进制补码表示。

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

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

于 2013-04-22T19:59:27.940 回答
21
var binary = (-3 >>> 0).toString(2); // coerced to uint32

console.log(binary);

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

jsfiddle上

输出是

11111111111111111111111111111101
-3 
于 2013-04-22T20:01:36.057 回答
4

.toString()旨在返回字符串表示中数字的符号。请参阅EcmaScript 2015,第 7.1.12.1 节

  1. 如果m小于零,则返回字符串“-”和 ToString(−<em>m) 的字符串连接。

当基数作为参数传递时,此规则没有什么不同,这可以从第 20.1.3.6 节得出结论:

  1. 使用radixNumber指定的基数返回此 Number 值的字符串表示形式。[...] 该算法应该是 7.1.12.1 中规定的算法的概括。

一旦理解了这一点,令人惊讶的是为什么它对-3 >>> 0.

这种行为实际上与 无关.toString(2),因为在调用它之前值已经不同:

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

这是>>>操作员行为方式的结果。

(在撰写本文时)有关mdn的信息并不完全正确,这也无济于事。它说:

所有位运算符的操作数都转换为二进制补码格式的有符号 32 位整数。

但这不适用于所有按位运算符。运算符是规则的>>>一个例外。从EcmaScript 2015 第 12.5.8.1 节中指定的评估过程可以清楚地看出这一点:

  1. lnum为 ToUint32( lval )。

ToUint32操作有一个步骤,操作数映射到无符号 32 位范围:

  1. int32bitint modulo 2 32

当您将上述模运算(不要与 JavaScript 的运算符混淆%)应用于示例值 -3 时,您确实会得到 4294967293。

由于 -3 和 4294967293 显然不是同一个数字,因此(-3).toString(2)(4294967293).toString(2).

于 2019-03-28T14:03:21.803 回答
2

在这里总结几点,如果其他答案有点混乱:

  • 我们要得到的是二进制表示的负数的字符串表示;这意味着字符串应该显示一个带符号的二进制数(使用 2 的补码)
  • 表达式(-3 >>> 0).toString(2),我们称它为 A,可以完成工作;但我们想知道它为什么以及如何工作
  • 如果我们使用var num = -3; num.toString(-3)我们会得到-11,这只是数字 3 的无符号二进制表示,前面有一个负号,这不是我们想要的
  • 表达式 A 的工作方式如下:

1)(-3 >>> 0)

>>>操作采用左操作数 (-3),它是一个有符号整数,并将位 0 ​​向左移动(因此位不变),以及与这些不变位对应的无符号数。

-3 >>> 0有符号数 -3 的位序列与无符号数 4294967293 的位序列相同,如果我们简单地输入REPL ,这就是节点给我们的。

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

现在,如果我们调用toString这个无符号数字,我们将只得到数字位的字符串表示,它与 -3 的位序列相同。

我们实际上所做的是说“嘿 toString,当我告诉您打印出无符号整数的位时,您的行为很正常,所以由于我想打印出有符号整数,所以我只需将其转换为无符号整数,然后你帮我打印出来。”

于 2020-01-04T09:19:10.287 回答
0

大安的回答很好地解释了这一点。

toString(2)并没有真正将数字转换为二进制补码,而是将数字简单地转换为正二进制形式,同时保留它的符号。

例子

Assume the given input is -15,
1. negative sign will be preserved
2. `15` in binary is 1111, therefore (-15).toString(2) gives output
-1111 (this is not in 2's complement!)

我们知道在 32 位中 -15 的 2 的补码是
11111111 11111111 11111111 11110001

因此,为了获得 (-15) 的二进制形式,我们实际上可以使用无符号右移将其转换为无符号 32 位整数>>>,然后将其传递给以toString(2)打印出二进制形式。这就是我们这样做的原因(-15 >>> 0).toString(2),它将为我们11111111111111111111111111110001提供 2 的补码中 -15 的正确二进制表示。

于 2021-04-14T05:33:06.877 回答