【问题标题】:How does bitwise AND OR and XOR works on -negative signed integers?按位 AND OR 和 XOR 如何作用于负符号整数?
【发布时间】:2018-12-27 19:41:34
【问题描述】:

我只是在bitwise operators 上解决随机问题,并尝试各种其他组合来制作个人笔记。不知何故,我就是想不出解决办法。

假设我想检查两个整数之间的按位与或检查 ~number 和 -negative number(~num1 & -num2) 以及各种其他组合。然后我可以看到答案,但我无法确定这是怎么发生的?

控制台:

console.log(25 & 3);输出 1(我可以轻松解决)。

console.log(-25 & -3);输出 27。

类似

console.log(~25 & ~3);输出 -28。

console.log(25 & ~3);输出 -24。

console.log(~25 & 3);输出 -2。

console.log(~25 & -3);输出 --28。

console.log(-25 & ~3);输出 --28。

我知道 "console.log(25 & -3)" 背后的逻辑。

25 是 11001
-3 是 11101(3=00011 减号就像 2s 恭维+1)
AND-11001 = 25。

但是当两个数字都是负数或上面提到的其他情况时,我不能让它以同样的方式工作。我也尝试过各种数字组合,而不仅仅是这两个。但我无法解决问题。谁能解释我无法解决的问题中使用的二进制逻辑。

(我在 SO 上花了大约 2 小时来找到答案,在 google 上又花了 1 小时以上,但我仍然没有找到答案)。

感谢和问候。

【问题讨论】:

  • 您是否检查过其他类似问题的问题?如果是这样,请参考它们并让我们知道为什么它不适合您。
  • 我确实试图找到可能相似的案例,但我没有找到任何会提出相同问题的案例。每个地方,甚至多个网站上,每个人都讨论过一个正整数和一个负整数的问题,这很容易做到。出于这个原因,与其浪费时间寻找,我想来这里。请放心,如果我得到与我类似的问题,我一定会标记你。 BTW @PSKocik 下面确实给出了一个非常简单而漂亮的例子。

标签: javascript bit-manipulation bitwise-operators bitwise-and


【解决方案1】:

JavaScript 指定对整数执行按位运算,就好像它们存储在二进制补码符号中一样。幸运的是,现在大多数计算机硬件本身都使用这种表示法。

为简洁起见,我将以下数字显示为 8 位二进制。它们在 JavaScript 中实际上是 32 位的,但是对于原始问题中的数字,这不会改变结果。但是,它确实让我们丢掉了很多前导位。

console.log(-25 & -3); //outputs -27. How?

如果我们把整数写成二进制,我们分别得到 (11100111 & 11111101)。将它们加在一起,得到 11100101,即 -27。

在您后面的示例中,您似乎可以互换使用 NOT 运算符 (~) 和否定 (-)。你不能在二进制补码中做到这一点:~ 和 - 不是一回事。 ~25 是 11100110,即 -26,而不是 -25。同样,~3 是 11111100,是 -4,而不是 -3。

但是当我们把这些放在一起时,我们可以计算出你给出的例子。

console.log(~25 & ~3); //outputs-28. How?

11100110 & 11111100 = 11100100,即 -28(不是你写的 28)

console.log(25 & ~3);//outputs-24. How?

00011001 & 11111100 = 00011000,即 24

console.log(~25 & 3);//outputs-2. How?

11100110 & 00000011 = 00000001,即 2

console.log(~25 & -3);//outputs--28. How?

11100110 & 11111101 = 11100100,即-28

console.log(-25 & ~3);//outputs--28. How?

11100111 & 11111100 = 11100100,即-28

理解这一点的真正关键是你并没有真正对整数使用按位运算。你在一定大小的比特包上使用它们,而这些比特包恰好可以方便地表示为整数。这是了解这里发生了什么的关键,因为您偶然发现了一个差异很重要的案例。

在计算机科学中的某些特定情况下,您可以通过 巧合 来操作比特包,从而得到与对数字进行特定数学运算时相同的结果。但这仅在特定情况下有效,并且它们要求您对正在处理的数字进行某些假设,如果您的数字符合这些假设,事情就会崩溃。

这也是 Donald Knuth 说“过早优化是万恶之源”的原因之一。如果您想使用按位运算代替实际的整数数学,您必须绝对确定您的输入实际上将遵循该技巧所需的假设。否则,当您开始使用这些假设之外的输入时,结果会开始看起来很奇怪。

【讨论】:

  • 正确,我忘了最后一位用作符号位。谢谢你的解释。
【解决方案2】:

-27 有 6 个二进制数字,所以你应该使用至少有那么多数字的数字。有了 8 位数字,我们就有:

  • 00011001 = 25
  • 00000011 = 3
  • 00011011 = 27

和:

  • 11100111 = -25
  • 11111101 = -3
  • 11100101 = -27

现在 -25 & -3 = -27 因为 11100111 & 11111101 = 11100101

【讨论】:

    【解决方案3】:

    可以通过以下方式找到 32 位整数的二进制字符串表示:

    (i >>> 0).toString(2).padStart(32, '0')
    

    两个二进制字符串的按位与运算很简单

    带符号的 32 位二进制字符串的整数值是

    parseInt(bitwiseAndString, 2)
    

    如果字符串以“0”开头,或者

    -~parseInt(bitwiseAndString, 2) - 1
    

    如果它以'1'开头

    将所有这些放在一起:

    const tests = [
      ['-25', '-3'],
      ['~25', '-3'],
      ['25', '~3'],
      ['~25', '3'],
      ['~25', '~3'],
      ['-25', '~3']
    ]
    
    const output = (s,t) => { console.log(`${`${s}:`.padEnd(20, ' ')}${t}`); }
    
    const bitwiseAnd = (i, j) => {
      console.log(`Calculating ${i} & ${j}`);
      const bitStringI = (eval(i) >>> 0).toString(2).padStart(32, '0');
      const bitStringJ = (eval(j) >>> 0).toString(2).padStart(32, '0');
      output(`bit string for ${i}`, bitStringI);
      output(`bit string for ${j}`, bitStringJ);
      const bitArrayI = bitStringI.split('');
      const bitArrayJ = bitStringJ.split('');
      const bitwiseAndString = bitArrayI.map((s, idx) => s === '1' && bitArrayJ[idx] === '1' ? '1' : '0').join('');
      output('bitwise and string', bitwiseAndString);
      const intValue = bitwiseAndString[0] === '1' ? -~parseInt(bitwiseAndString, 2) - 1 : parseInt(bitwiseAndString, 2);
      if (intValue === (eval(i) & eval(j))) {
        console.log(`integer value: ${intValue} ✓`);
      } else {
        console.error(`calculation failed: ${intValue} !== ${i & j}`);
      }
    }
    
    tests.forEach(([i, j]) => { bitwiseAnd(i, j); })

    【讨论】:

    • 复制代码以供我的朋友将来参考。谢谢。
    【解决方案4】:

    25 = 16+8+1 = 0b011001,我添加了另一个 0 位作为符号位。实际上,您至少有 8 个二进制数字 但两者的补码数学是相同的。要在 6 位二进制补码中获得 -25,您可以使用 -25 = ~25 + 1=0b100111 3=2+1=0b000011; -3 = ~3+1 = 0b111101

    当你&这两个时,你会得到:

    -25 = ~25 + 1=0b100111
    &
    -3 = ~3 + 1 = 0b111101
                  0b100101
    

    最左边的位(符号位)设置为负数。要找到它的负数,您可以反转过程并首先减去 1,然后执行~

    ~(0b100101-1) = 0b011011
    

    那是1+2+0*4+8+16 = 27 所以-25&-3=-27

    对于 25 和 ~3,它是:

    25 = 16+8+1 = 0b011001
    &        ~3 = 0b111100
    ______________________
                  0b011000 = 24
    

    对于 ~25 和 3,它是:

    ~25 =         0b100110
    &        ~3 = 0b000011
    ______________________
                  0b000010 = 2
    

    对于 ~25 & -3,它是:

    ~25 =         0b100110
    &      ~3+1 = 0b111101
    ______________________
                   0b100100 #negative
    
    #find what it's a negative of:
    ~(0b100100-1) =~0b100011 = 0b011100 = 4+8+16 = 28
                   0b100100 = -28
    

    【讨论】:

    • 你的答案是我没想到的最简单的答案。我在一分钟内理解了它并尝试解决另一个问题并得到了确切的结果。感谢您的解决方案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-12
    • 1970-01-01
    • 1970-01-01
    • 2015-05-09
    • 2021-12-05
    相关资源
    最近更新 更多