在Java中进行上转型或移位时抑制符号扩展

9

我有一种感觉,这是一个相当琐碎的问题,但我被难住了。在我的应用程序中,我使用一对整数作为查找表的键。我认为将这两个整数连接成一个长整型并使用单个长整型作为键会更容易。来自C背景的我希望像这样的东西能够工作:

int a, b;
long l = (long)a << 32 | b;

我的尝试在Java中复制这个操作让我感到沮丧。特别是因为没有无符号整数类型,我似乎无法避免b的自动符号扩展(a被左移以使其无关紧要)。我尝试使用b & 0x00000000FFFFFFFF,但令人惊讶的是它没有任何效果。我还尝试了相当丑陋的(long)b << 32 >> 32,但它似乎被编译器优化掉了。
我希望能严格使用原始数据类型进行位运算来完成此操作,但我开始怀疑是否需要使用某种缓冲对象来实现这一点。

4
(long)b << 32 >> 32没有被优化掉。主要问题是>>是算术右移(sra),而>>>是逻辑右移(srl),你需要的是:(long)b << 32 >>> 32 - ide
表达式 b & 0x00000000FFFFFFFF 必须正常工作,但是为什么你要用那些零来混淆它呢? - maaartinus
@maaartinus:我对Java不够熟悉,不知道它内部将十六进制字面量存储为什么。我知道在C中,十六进制字面量是无符号的,但我担心0xFFFFFFFF会被符号扩展。回想起来,我只需要一个前导零来满足这个问题。尽管如此,表达式没有任何效果。 - Oscar Korz
2
当然, 0xFFFFFFFF 不会被符号扩展。但是还有一个问题,它只是一个 int(我们都忘了 L 后缀),并通过符号扩展晋升为 long。这就是为什么它不能工作的原因。前导零不起任何作用,文字只是一个 int。看看我的答案,肯定是对的,我用过很多次。 - maaartinus
你所做的就像 C 语言中的 (signed int) (b & 0xFFFFFFFF)。结果是一个有符号整数,并通过符号扩展提升为长整型。 - maaartinus
我没有想到字面值会被保存为整数。这就解释了为什么掩码没有起作用。 - Oscar Korz
2个回答

21

我总是使用我的实用类与

public static long compose(int hi, int lo) {
    return (((long) hi << 32) + unsigned(lo));
}
public static long unsigned(int x) {
    return x & 0xFFFFFFFFL;
}

public static int high(long x) {
    return (int) (x>>32);
}
public static int low(long x) {
    return (int) x;
}

对于任意的 int x, y(无论是负数还是非负数)

high(compose(x, y)) == x
low(compose(x, y)) == y

对于任何long z,它都包含并保持

compose(high(z), low(z)) == z

也具有保持的功能。


如果可以的话,我会给你点赞超过一次的,符号扩展让我感到疯狂。 - Michael
“high” 应该使用 >>> 而不是 >>,对吗? - James Ko
@JamesKo 右移后的值仅在其高32位中有所不同,即那些被强制转换截断的位。您可能希望使用无符号移位来证明您没有处理有符号性,但我选择使用有符号移位来证明这并不重要。 ;) - maaartinus
1
从Java 1.8开始,现在有Integer.toUnsignedLong来进行这种转换。 - dworvos

1

我偶尔会这样做 - 我将两个整数存储在一个长整型中,用于表示我的X和Y坐标。因为我知道我的范围永远不会超过10亿,所以我会这样处理:

private Long keyFor(int x, int y) {
    int kx = x + 1000000000;
    int ky = y + 1000000000;
    return (long)kx | (long)ky << 32;
}

private Long keyFor(int[] c) {
    return keyFor(c[0],c[1]);
}

private int[] coordsFor(long k) {
    int x = (int)(k & 0xFFFFFFFF) - 1000000000;
    int y = (int)((k >>> 32) & 0xFFFFFFFF) - 1000000000;
    return new int[] { x,y };
}

这不是通用解决方案,使组合难以辨认。以正常方式组成数字可以工作得更快,并允许直接在十六进制输出中查看组件。 - maaartinus
“按照正常方式组合数字”并没有起作用 - 如果它起作用的话,我就不会选择这条路了。这正是 OP 遇到的确切问题。而且它并不难理解。我肯定不认为这是一个应该被点踩的标准,因为这是一个潜在的解决方案,即使你觉得它比必要的更加混乱。 - corsiKa
我不喜欢它,因为它引入了不必要的混淆。按照“正常方式”操作可以正常工作,假设你像我一样防止符号扩展。然后,您无需添加任何常量,也无需对所涉及的数字做任何假设。 - maaartinus
通过不提供常数,您仅限于正数。因此,无论您喜欢与否,实际上都在对您的数字进行假设。 - corsiKa
我不限制自己使用正数!请看我的修改后的答案,试着找出反例。 - maaartinus
好的,我会承认功能方面的问题。但我坚信,虽然我的代码包含了一些不必要的逻辑,但它并没有过度混淆。其中的逻辑比你的代码更容易理解,因为理解曲线是在阅读位移时设置的。加法也很简单。在这个项目中,我实际上放弃了它,转而使用class Coordinate { private int x; private int y; // etc } - corsiKa

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