在Java中,位运算符到底是如何工作的?

9

我目前正在努力理解Java中的位运算符和位移操作符。虽然它们在简化的玩具示例(基本上是正整数)中对我来说很有意义,但是一旦涉及到负数或其他情况时,我的理解就会崩溃。我尝试用两个搜索引擎在互联网上搜索,甚至查看了Java规范文档,但找不到任何正确描述Java中如何使用位运算符和位移操作符的来源。

Java标准库中一个特别令我困惑的函数是java.lang.Integer.toUnsignedLong(int)。OpenJDK的源代码如下所示(LGPLv2带classpath例外),其中包含摘自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;
}

根据上面的官方文档,“长整型的高32位为零,低32位等于整型参数的位。”然而,我没有看到这如何从方法体内的代码中得出。
对于正数x,我的想法如下: 1. 当整数强制转换为长整型时,其符号位/最高有效位为零。因此,长整型的符号位/最高有效位为零,低位与整数相同。 2. 由于长整型的0xffffffff在最低4个字节中都为1,并且只有这些字节中有数据,因此该掩码没有影响,返回了正确的结果。
但是,当考虑负数x时,我就无法理解了: 1. 当整数强制转换为长整型时,其符号位/最高有效位为1。因此,长整型的符号位/最高有效位也为1,低位与整数相同,除了第四个最低有效字节的最高有效位为0,而这个位在整数中为1。 2. 由于长整型的0xffffffff在最低4个字节中都为1,在最高4个字节中都为零,它唯一的作用是改变长整型的符号位,并保留错误的整数在最低4位中。因此,这个方法返回了一个错误的答案,其中整数的符号位在移动到长整型时被改变了。
然而,当我测试这个方法时,得到的结果与Javadoc一致。我怀疑我对Java中的位运算符或其二进制补码整数表示法中的一个或多个基本要点存在误解,并希望这个问题能够澄清这些要点。

4
当你将负的整型数(int)转换为长整型数(long)时,高32位将填充为1。执行“&”操作会将这些位重新变成0。 - Dawood ibn Kareem
也许这可以帮助:System.out.printf("%s & %s = %s%n", Long.toBinaryString((long) x), Long.toBinaryString(0xffffffffL), Long.toBinaryString(((long)x) & 0xffffffffL)); - Elliott Frisch
@DawoodibnKareem 如果前32位填满了1,那么long的大小将变得非常巨大,可能接近其最大值。在Java中,从int到long的转换应该保持相同的数值。因此,我的理解与您关于前32位填充为1的说法存在冲突。您能否澄清一下? - john01dav
最高位是负数。它的值(按数量级计算)比其他所有位加起来还多一个。因此,这些1主要会互相抵消。 - Dawood ibn Kareem
3
@john01dav,您对整个情况的描述,包括您已经了解的内容以及您认为会发生的事情,都非常出色。我希望在 Stack Overflow 这里的每个问题都能以这种方式编写。 - Roland Illig
显示剩余3条评论
1个回答

6

按照预期,位运算符能够正常工作。它们是严格的位运算符,完全不考虑位的语义。

有时候使用断点来运行代码是最简单的方法。对于您的特定示例,我将操作步骤转换为原子语句,并使用 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

第一步是操作看起来像这样的主要原因。在Java中,所有(严格的数字)值都是带符号的(char是无符号的)。这意味着,正如您所正确说明的那样,所有最高位都是符号位。但有趣的部分是,如果一个数是负数,则其余的位做什么。 以下线程已经涵盖了“二进制补码”的基础知识: 什么是“2的补码”? 所以这个维基百科页面也一样: 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。因此,它们必须被删除。


1
微调一下:chars 也是无符号的。除此之外,写得很好! - yshavit
1
正确。谢谢你提到了这一点。我更明确地阐述了我的意思。 :) - TreffnonX

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接