Java中无符号右移操作符">>>"的目的是什么?

34

我理解 Java 中无符号右移运算符 ">>>" 的作用,但为什么我们需要它呢?而且为什么不需要相应的无符号左移运算符呢?


1
可能是>>>和>>之间的区别的重复问题。 - Amit G
6个回答

30
>>>运算符允许您将intlong视为32位和64位的无符号整数类型,这是Java语言中缺少的。
当您移位的对象不表示数字值时,这非常有用。例如,您可以使用32位int表示黑白位图图像,其中每个int编码了屏幕上的32个像素。如果您需要向右滚动图像,则希望int左侧的位变为零,以便您可以轻松地将相邻int中的位放入其中。
 int shiftBy = 3;
 int[] imageRow = ...
 int shiftCarry = 0;
 // The last shiftBy bits are set to 1, the remaining ones are zero
 int mask = (1 << shiftBy)-1;
 for (int i = 0 ; i != imageRow.length ; i++) {
     // Cut out the shiftBits bits on the right
     int nextCarry = imageRow & mask;
     // Do the shift, and move in the carry into the freed upper bits
     imageRow[i] = (imageRow[i] >>> shiftBy) | (carry << (32-shiftBy));
     // Prepare the carry for the next iteration of the loop
     carry = nextCarry;
 }

上面的代码不关注前三位的内容,因为 >>> 运算符会将它们移除。

因为有符号数据类型和无符号数据类型进行左移操作是相同的,所以没有对应的 << 运算符。


“treat”是什么意思?我在我的代码中一直使用32位无符号值,从来不使用>>>运算符。 - Tyler Durden
5
如果你使用的是>>,而不是带有>>>的操作符,那么对于32位或64位值的最高有效位(也称为“符号位”)将会得到特殊处理 - 它会在移位之后被复制到新的符号位上,因此在>>移位后,值的符号保持不变。但这个处理方式对于一些语言中的无符号类型来说是不同的,比如C/C++或C#。在这些语言中,符号位的内容由第一个操作数的类型控制。由于Java中没有无符号类型,所以该语言引入了一个专门的操作符来进行无符号右移操作。 - Sergey Kalinichenko

17

>>>也是一种安全高效的方法,用于找到两个(大)整数的平均值并四舍五入:

int mid = (low + high) >>> 1;

如果整数highlow接近于最大的机器整数,那么上述语句将是正确的,但是

int mid = (low + high) / 2;

由于溢出可能会导致错误的结果。

这里是一个使用示例,修复了一个简单二分查找中的错误。


1
找到平均值的好信息。但是,“将引发异常” - 不会引发异常,但由于溢出而导致意外结果。 - Mani
1
很好的解释!Joshua Bloch在“Extra, Extra - Read All About It: Nearly All Binary Searches and Mergesorts are Broken”中给出了一个恰好使用这种方法的例子,修复了二分查找中的一个错误。 - Nils von Barth

4
一个普通的右移>>对于一个负数来说,仍然会保持它是负数。也就是说,符号位将被保留。
一个无符号的右移>>>也会移动符号位,但用0位替换它。
没有必要有相应的左移,因为只有一个符号位,它是最左边的位,所以只有在向右移动时才会干扰。
本质上,它们之间的区别在于一个保留符号位,另一个用0位替换符号位。
对于正数,它们的作用是相同的。
有关同时使用>>>>>的示例,请参见BigInteger shiftRight

好的,正如我所说,我知道它是做什么的。为什么?在什么情况下你关心这个? - Tyler Durden
@TylerDurden - 以 BigInteger 为例,它使用整数数组来存储数字。要向右移位,它将使用 >>> 向右移动除最高有效数字外的所有数字。 - OldCurmudgeon

3
签名右移运算符在IT技术中很有用,如果一个表示数字的int需要除以2的幂并向负无穷舍入,则可以使用该运算符。这在缩放坐标以进行显示等操作时非常方便;不仅比除法更快,而且在缩放前后差异为比例因子的坐标在缩放后只会相差一个像素。如果使用除法而不是移位,则会出现问题。例如,当缩放因子为2时,-1和+1之间的差别为2,在缩放后应该相差1,但是-1/2=0,1/2=0。如果使用签名右移,则结果很好:-1>>1=-1,1>>1=0,正确地产生相差一个像素的值。
无符号运算符在以下情况下很有用:输入预期只有一个位设置,并且希望结果也是如此,或者将使用循环输出字中的所有位并希望它能清晰地终止。例如:
void processBitsLsbFirst(int n, BitProcessor whatever)
{
  while(n != 0)
  {
    whatever.processBit(n & 1);
    n >>>= 1;
  }
}

如果代码使用带符号的右移操作并传递了负值,它将无限输出1。然而,使用无符号右移运算符时,最高有效位被解释为任何其他位一样。
当计算产生0到4294967295之间的正数,并且希望将该数字除以2的幂时,无符号右移运算符也可能很有用。例如,在计算已知为正的两个int值的总和时,可以使用(n1+n2)>>>1而不必将操作数提升为long。此外,如果要将正的 int 值除以像 pi 这样的值而又不使用浮点数,可以计算((value*5468522205L)>>>34)[(1L<<34)/pi是5468522204.61,四舍五入后得到5468522205]。对于大于1686629712的被除数,计算value*5468522205L将产生一个“负”值,但由于已知正确的算术值为正,因此使用无符号右移将允许使用正确的正数。

3

基本上,这与符号位(数字移位)或无符号移位(通常是像素相关的东西)有关。

由于左移不管怎样都不涉及符号位,所以 <<< 和 << 是一样的...

无论如何,我还没有遇到过需要使用 >>> 的人,但我相信他们正在做一些惊人的事情。

正如您刚才看到的,>> 运算符每次移位时都会自动用其先前内容填充高位比特。这保留了值的符号。但有时这是不可取的。例如,如果您要移动的内容不表示数值,则可能不希望发生符号扩展。在这种情况下,当您使用基于像素的值和图形时,通常会想将零移入高位比特,无论其初始值如何。这被称为无符号移位。为了实现这一点,您将使用 java 的无符号右移运算符 >>>,它总是将零移入高位比特。

进一步阅读:

http://henkelmann.eu/2011/02/01/java_the_unsigned_right_shift_operator

http://www.java-samples.com/showtutorial.php?tutorialid=60


实际上,在Java中没有<<<运算符。 - Ted Hopp
根本不需要<<<运算符 :) - Dory Zidon
确实。我是在评论你的措辞,这可能会让新手产生误解:“它们是一样的东西(<<<<<)”。 - Ted Hopp
当您移植对无符号整数进行右移的C代码时,最好使用>>>,否则会得到错误的结果。 - Ingo

0
在Java领域中,避免溢出的常见方法是使用强制类型转换或大整数处理,例如在前面的示例中将int转换为long。
int hiint = 2147483647;
System.out.println("mean hiint+hiint/2 = " + ( (((long)hiint+(long)hiint)))/2);
System.out.println("mean hiint*2/2 = " + ( (((long)hiint*(long)2)))/2);

BigInteger bhiint = BigInteger.valueOf(2147483647);
System.out.println("mean bhiint+bhiint/2 = " + (bhiint.add(bhiint).divide(BigInteger.valueOf(2))));

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