java :int 的移位距离限制为 31 位
java : shift distance for int restricted to 31 bits
知道为什么 java 中 int 的移位距离被限制为 31 位(右侧操作数的低 5 位)吗?
http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.19
x >>> n
我可以看到类似的问题java Bit operations >>> shift但没有人指出正确答案
移位距离限制为 31 位,因为 Java int 有 32 位。将 int
数字移位超过 32 位将产生相同的值(0 或 0xFFFFFFFF
,具体取决于初始值和您使用的移位操作)。
这是一个设计决定,但至少对于某些用例来说似乎有点不幸。首先是一些术语:我们将定义为零所有移位量大于移位字中位数的方法称为饱和方法,并且Java 方法仅使用底部 5(或 6 个 long
)位来定义移位量作为 mod 方法。
您可以通过列出有用的 偏移值来查看问题。这些是导致唯一输出值 1 的移位量。如果采用 >>>,有趣的值是 0 到 32(含)。 0 导致值不变,而 32 导致 0。比 32 移动 更多 会再次产生与 32 相同的结果,当然 - 但 java 甚至不让你移动 32:它停在 31!移动 32 可能会出乎意料地保持您的值不变。
在 >>>
的许多用途中,不可能移动 32,或者 Java 行为有效。然而,在其他情况下,自然结果是 32,您必须将零作为特殊情况。
至于为什么他们会选择那个设计?好吧,这可能有助于当时常见的 PC 硬件(x86,就像今天一样)以完全相同的方式实现移位(仅使用最后 5 位进行 32 位移位,最后 6 位用于 64 位移位)。因此移位可以直接映射到硬件,无需任何特殊情况、条件移动或分支2.
此外,对于默认情况下不实现这些语义的硬件,很容易通过一个简单的掩码获得Java语义:shiftAmount & 0x1F
。这在所有硬件上都会很快。反向映射 - 在不支持它的硬件上实现 饱和 移位更复杂:您可能需要昂贵的比较和分支,一些微不足道的技巧或预测动作来处理 > 31
案例.
最后,mod 方法对于许多算法来说是很自然的。例如,如果您正在实现一个位图结构,每个位可寻址,一个好的实现可能是有一个整数数组,每个整数代表 32 位。在内部索引到第 N 位,您可以将 N 分成两部分 - 高 27 位会在数组中找到该位所在的字,低 5 位会从字中挑选出该位。要从 word
中取出位(例如,将其移动到 LSB),您可以这样做:
int val = (word >>> (index & 0x1F)) & 1
如果该位已设置,则将 val
设置为 1,否则设置为 0。但是,由于指定 Java >>>
运算符的方式,您根本不需要 & 0x1F
部分,因为它已经隐含在 mod定义!所以你可以省略它,而且 JDK 的 BitSet
确实使用了这个技巧。
1 当然,任何在 MSB 中没有 1 的值在 >>>
下可能不会产生唯一值,一旦所有的 1 都被移开,让我们谈谈关于带有前导值的任何值。
2 为了它的价值,我检查了 ARM 和 semantics are even weirder:对于变量移位,底部 8 位 的移位量被使用。所以这个转变是一个奇怪的混合体——一旦你超过 31,它实际上是一个饱和转变,但 只有 到 255,此时它循环并突然具有非零值接下来的 31 个值等
知道为什么 java 中 int 的移位距离被限制为 31 位(右侧操作数的低 5 位)吗?
http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.19
x >>> n
我可以看到类似的问题java Bit operations >>> shift但没有人指出正确答案
移位距离限制为 31 位,因为 Java int 有 32 位。将 int
数字移位超过 32 位将产生相同的值(0 或 0xFFFFFFFF
,具体取决于初始值和您使用的移位操作)。
这是一个设计决定,但至少对于某些用例来说似乎有点不幸。首先是一些术语:我们将定义为零所有移位量大于移位字中位数的方法称为饱和方法,并且Java 方法仅使用底部 5(或 6 个 long
)位来定义移位量作为 mod 方法。
您可以通过列出有用的 偏移值来查看问题。这些是导致唯一输出值 1 的移位量。如果采用 >>>,有趣的值是 0 到 32(含)。 0 导致值不变,而 32 导致 0。比 32 移动 更多 会再次产生与 32 相同的结果,当然 - 但 java 甚至不让你移动 32:它停在 31!移动 32 可能会出乎意料地保持您的值不变。
在 >>>
的许多用途中,不可能移动 32,或者 Java 行为有效。然而,在其他情况下,自然结果是 32,您必须将零作为特殊情况。
至于为什么他们会选择那个设计?好吧,这可能有助于当时常见的 PC 硬件(x86,就像今天一样)以完全相同的方式实现移位(仅使用最后 5 位进行 32 位移位,最后 6 位用于 64 位移位)。因此移位可以直接映射到硬件,无需任何特殊情况、条件移动或分支2.
此外,对于默认情况下不实现这些语义的硬件,很容易通过一个简单的掩码获得Java语义:shiftAmount & 0x1F
。这在所有硬件上都会很快。反向映射 - 在不支持它的硬件上实现 饱和 移位更复杂:您可能需要昂贵的比较和分支,一些微不足道的技巧或预测动作来处理 > 31
案例.
最后,mod 方法对于许多算法来说是很自然的。例如,如果您正在实现一个位图结构,每个位可寻址,一个好的实现可能是有一个整数数组,每个整数代表 32 位。在内部索引到第 N 位,您可以将 N 分成两部分 - 高 27 位会在数组中找到该位所在的字,低 5 位会从字中挑选出该位。要从 word
中取出位(例如,将其移动到 LSB),您可以这样做:
int val = (word >>> (index & 0x1F)) & 1
如果该位已设置,则将 val
设置为 1,否则设置为 0。但是,由于指定 Java >>>
运算符的方式,您根本不需要 & 0x1F
部分,因为它已经隐含在 mod定义!所以你可以省略它,而且 JDK 的 BitSet
确实使用了这个技巧。
1 当然,任何在 MSB 中没有 1 的值在 >>>
下可能不会产生唯一值,一旦所有的 1 都被移开,让我们谈谈关于带有前导值的任何值。
2 为了它的价值,我检查了 ARM 和 semantics are even weirder:对于变量移位,底部 8 位 的移位量被使用。所以这个转变是一个奇怪的混合体——一旦你超过 31,它实际上是一个饱和转变,但 只有 到 255,此时它循环并突然具有非零值接下来的 31 个值等