【问题标题】:How, exactly, do bitwise operators work in Java?确切地说,按位运算符在 Java 中是如何工作的?
【发布时间】:2019-08-31 10:30:04
【问题描述】:

我目前正在尝试围绕 Java 中的按位和位移运算符。尽管它们在简化的玩具示例(基本上是正整数)中对我有意义,但一旦涉及负数,我的理解就会崩溃,在其他一些情况下。我尝试使用两个搜索引擎在整个 Internet 上进行搜索,甚至还检查了 Java 规范。我找不到任何正确描述按位和位移运算符如何在 Java 中工作的来源。

Java 标准库中让我特别困惑的一个函数是java.lang.Integer.toUnsignedLong(int)。此处显示了 OpenJdk 的源代码(带有类路径异常的 LGPLv2),并在 Javadoc 中摘录:

/**
 * Converts the argument to a {@code long} by an unsigned
 * conversion.  In an unsigned conversion to a {@code long}, the
 * high-order 32 bits of the {@code long} are zero and the
 * low-order 32 bits are equal to the bits of the integer
 * argument.   
 */
public static long toUnsignedLong(int x) {
    return ((long) x) & 0xffffffffL;
}

根据上面转载的官方文档,“long 的高 32 位为零,低 32 位等于整数参数的位。”但是,我看不出这是如何从方法主体内的代码得出的。

阅读方法时,我对正x的思路如下:

  1. 当整数转换为长整数时,其符号位/最高有效位为零。因此,long 的符号位/最高有效位为零,低位等于整数。
  2. 由于long 0xffffffff在最低4个字节中都是1,并且因为只有这些字节会有数据,所以这个掩码没有作用,返回正确的结果。

然而,当在否定 x 的背景下阅读它时,我的理解就崩溃了:

  1. 当整数为 cst 到 long 时,其符号位/最高有效位为 1。因此,long 的符号位/最高有效位为 1,低位与整数的符号位相同,但在整数中为 1 时,第四个最低有效字节的最高有效位为零。
  2. 由于long0xffffffff的低4字节全为1,高4字节全为0,所以它的唯一作用是改变long的符号位,并保持不正确的整数在四个最低有效位完好无损。因此,它会从此方法返回错误的答案,其中整数的符号位在移入长整数时会发生变化。

但是,当我测试此方法时,我得到的结果与 Javadoc 一致。我怀疑我误解了关于 Java 中的位运算符或其二进制补码整数表示的一个或多个基本点,我希望这个问题可以澄清这些点。

【问题讨论】:

  • 0xffffffff 不全是 1。每个ff 是一个字节,这样的对有四对。 long 是 8 个字节。所以它是 4 个字节(32 位)的 0,然后是 4 个字节的 1。这有帮助吗?
  • @yshavit 这是一种误解(或者,更确切地说,是脑子放屁——我确实知道你在说什么,但由于某种原因我没有应用它),但这并不能说明一切加起来。我已经编辑了问题以纠正这种误解。
  • 当您将否定的 int 转换为 long 时,前 32 位会被 1 填满。执行& 操作会将这些值变回零。
  • 也许这会有所帮助System.out.printf("%s & %s = %s%n", Long.toBinaryString((long) x), Long.toBinaryString(0xffffffffL), Long.toBinaryString(((long)x) & 0xffffffffL));
  • @john01dav 你对整个情况的描述,包括你已经知道的和你认为会发生的事情,这一切都很棒。我希望 Stack Overflow 上的每个问题都以这种风格写出来。

标签: java bit-manipulation language-lawyer bitwise-operators


【解决方案1】:

按位运算符的工作方式完全符合您的预期。它们是严格的位运算符,根本不考虑位的语义。

有时使用断点运行代码是最容易的。对于您的具体示例,我将操作步骤转换为原子语句并使用Long.toString 打印结果。

int x = -57;

// step 1:
long xCast = (long) x;
System.out.println(Long.toString(xCast, 2)); // -1110011 - this is not the bitwise representation however.

long mask = 0xffffffffL;
System.out.println(Long.toString(mask, 2)); // 11111111111111111111111111111111

// step 2:
long result = ((long) x) & mask;
System.out.println(Long.toString(result, 2)); // 11111111111111111111111111000111

步骤 1 是操作看起来如此的主要原因。在 Java 中,所有(严格数字)值都是有符号的(字符是无符号的)。这意味着,正如您正确指出的那样,所有最高位都是符号位。然而,有趣的部分是其余位的作用,如果一个数字是负数。 以下线程已经涵盖了“双补”的基础知识: What is “2's Complement”? 这个维基百科页面也是如此:https://en.wikipedia.org/wiki/Two%27s_complement

简而言之,在 java 中,对于整数:

int zero = 0; // == 0b00000000_00000000_00000000_00000000

int maxPositive = Integer.MAX_VALUE; // == 0b01111111_11111111_11111111_11111111

int minus1 = -1; // == 0b11111111_11111111_11111111_11111111

int minNegative = Integer.MIN_VALUE; // == 0b10000000_00000000_00000000_00000000

所以一切正常的原因是因为如果整数是负数,当它被强制转换时,整个高 32 位都被转换为 1,因为否则数字的表示值会改变。有效:

int x = 0b11111111_11111111_11111111_11000111;

被转换为:

long xCast = 0b11111111_11111111_11111111_11111111_11111111_11111111_11111111_11000111;

由于您作为开发人员希望该方法仅返回最初设置的位,因此您必须从结果中屏蔽高位。这是在第 2 步中完成的。

因此,您的示例的答案是:Java 中非浮点值的表示是二进制补码,因此,当将值从 int 智能转换为 long 时,高位用 1 填充负数。因此它们必须被删除。

【讨论】:

  • 轻微调整:chars 也未签名。除此之外,写得很好!
  • 正确。谢谢你提到它。我更明确地阐明了我的意思。 :)
猜你喜欢
  • 1970-01-01
  • 2011-02-10
  • 2015-09-03
  • 1970-01-01
  • 2012-06-10
  • 1970-01-01
  • 2010-10-21
  • 2014-01-17
  • 1970-01-01
相关资源
最近更新 更多