【问题标题】:Bitwise negation gives unexpected result按位取反会产生意想不到的结果
【发布时间】:2013-03-22 16:38:21
【问题描述】:

我正在尝试用 java 编写一个按位计算器,您可以输入一个表达式,例如 ~101,但是当我运行此代码时它会返回 10

import java.util.Scanner;

public class Test
{
    public static void main(String[] args)
    {
        Integer a = Integer.valueOf("101", 2);
        System.out.println(Integer.toString(~a,2));
    }
}

它输出 -110 为什么?

【问题讨论】:

标签: java bit-manipulation


【解决方案1】:

您假设 101 是 3 位长。 Java 不支持变长位操作,它对整个位 int 进行操作,因此 ~ 将是 32 位长“101”的 not

--- 在被问到“我该如何解决这个问题?”后编辑。 ---

这是一个非常好的问题,但答案是“你不能”和“你可以通过不同的方式实现相同的目标”的混合体。

您无法修复 ~ 运算符,因为它会做它所做的事情。这有点像要求修复 + 以仅添加 1 的位置。只是不会发生。

您可以实现所需的操作,但您需要更多的“东西”来实现它。首先,您必须有 something(另一个 int)来指定 感兴趣的位。这通常称为位掩码

 int mask = 0x00000007; // just the last 3 bits.

 int masked_inverse = (~value) & mask;

请注意,我们所做的实际上是反转 32 位,然后将其中的 29 位归零;因为,它们在掩码中被设置为零,这意味着“我们不关心它们”。这也可以想象为利用& 运算符,这样我们说“如果设置并且我们关心它,就设置它”。

现在你将仍然有 32 位,但只有低 3 位会被反转。如果你想要一个 3 位数据结构,那就另当别论了。 Java(和大多数语言)只是不直接支持这些东西。因此,您可能会想向 Java 添加另一个 type 来支持它。 Java 通过 class 机制添加类型,但内置类型是不可更改的。这意味着您可以编写一个类来表示 3 位数据结构,但它必须在内部将整数作为 32 位字段处理。

幸运的是,已经有人这样做了。它是标准 Java 库的一部分,is called a BitSet

BitSet threeBits = new BitSet(3);
threeBits.set(2);  // set bit index 2
threeBits.set(0);  // set bit index 0
threeBits.flip(0,3);

但是,由于 Java 中的类/对象系统的限制,这种位操作对他们有不同的感觉,这是因为定义类是在 Java 中添加新类型的唯一方法

【讨论】:

  • @JoshSobel 我更新了答案。一些非常短的问题需要很长的解释,因为它们很幸运能够打开新的理解之门。祝你工作顺利。
  • 但是我将如何动态调整掩码以适应给定的数字
  • @JoshSobel 如果您正在动态更新掩码,那么您无法比较不具有相同固定 number 位已用位的数字;但是,要回答您的问题,使用 int 掩码解决方案,只需将掩码变量重置为新掩码。使用 BitSet 解决方案,创建一个新的位集,然后在其中设置与在另一个位集中设置的相同位。我无法想象任何一种解决方案都是解决任何特定问题的正确答案,除非重置掩码还需要您重置值。
  • 乔希,由于输入字符串的长度,您不知道输入数字中的位数吗?我认为这会解决你的问题。 Integer 提供了一种从 int 生成二进制字符串的方法,它只需要它需要的长度(因此 int x = 3 会给你 11 的二进制字符串,长度为 2。)
【解决方案2】:

如果a = ...0000101 (bin) = 5 (dec)

~a = ~...0000101(bin) = ...1111010(bin) 

而Java使用“Two's complement”形式来表示负数所以

~a = -6 (dec)

现在Integer.toBinaryString(number)Integer.toString(number, 2) 的负数区别在于

  • toBinaryString 以“Two's complement”形式返回字符串,但
  • toString(number, 2) 计算二进制形式,如果数字是正数,如果参数是负数,则添加“减号”标记。

所以toString(number, 2) 对于~a = -6

  1. 计算6的二进制值 -> 0000110,
  2. 修剪前导零 -> 110,
  3. 添加减号 -> -110.

【讨论】:

    【解决方案3】:

    整数中的 101 实际上表示为 00000000000000000000000000000101 否定这个,你得到 11111111111111111111111111111010 - 这是 -6

    【讨论】:

    • 我认为 OP 想知道为什么 ~a 的二进制表示是 ...111111010, toString(~a,2) 打印 -110
    【解决方案4】:

    toString() 方法将其参数解释为有符号值。

    为了演示二进制操作,最好使用Integer.toBinaryString()。它将其参数解释为无符号,因此 ~101 输出为 1111111111111111111111111111010。

    如果您想要更少的输出位,您可以使用 & 屏蔽结果。

    【讨论】:

      【解决方案5】:

      稍微详细说明 Edwin 的回答 - 如果您希望创建一个可变长度掩码来开发感兴趣的位,您可能需要一些辅助函数:

      /**
       * Negate a number, specifying the bits of interest.
       * 
       * Negating 52 with an interest of 6 would result in 11 (from 110100 to 001011).
       * Negating 0 with an interest of 32 would result in -1 (equivalent to ~0).
       * 
       * @param number the number to negate.
       * @param bitsOfInterest the bits we're interested in limiting ourself to (32 maximum).
       * @return the negated number.
       */
      public int negate(int number, int bitsOfInterest) {
          int negated = ~number;
          int mask = ~0 >>> (32 - bitsOfInterest);
          logger.info("Mask for negation is [" + Integer.toBinaryString(mask) + "]");
          return negated & mask;
      }
      
      /**
       * Negate a number, assuming we're interesting in negation of all 31 bits (exluding the sign).
       * 
       * Negating 32 in this case would result in ({@link Integer#MAX_VALUE} - 32).
       * 
       * @param number the number to negate.
       * @return the negated number.
       */
      public int negate(int number) {
          return negate(number, 31);
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-04-05
        • 1970-01-01
        • 2019-04-07
        • 2015-03-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多