ビッグデータJava基礎——シフト演算の真実剖析(一)


レンガを投げて玉を引く:
Javaでは、左シフト演算子「<<」、右シフト演算子「>」、符号なし右シフト演算子「>>」の3種類のシフト演算子が定義されています.シフト演算では、シフト演算の両側の操作数は、byte、short、char、int、longタイプ、またはボックスを分解して整数に変換する必要があります.オペランドのタイプがbyte、short、charの場合、自動的にintタイプに昇格し、演算の結果もintタイプになります.シフト演算に対して、人々はその「誤解」が深すぎる......
自己ビット数を超えるシフト
intタイプは4バイト,32ビット,longタイプは8バイト,64ビットを占有することを知っている.では、intタイプ(longタイプ)を31ビット(63ビット)以上移動すると意味がなくなり、通俗的に言えば「全移動した」からだ.しかし、幸いなことに、左側のオペランドがintタイプ(byte、charとshortタイプが自動的にintタイプに昇格)またはlongタイプである場合、右側のオペランドが31または63より大きい場合、システムは関連処理を行った.
どのように処理されていますか?一般的には、左側のオペランドがintタイプ(リフト後のintタイプを含む)の場合、右側のオペランドに対して32の除算を行い、左側のオペランドがlongタイプの場合、右側のオペランドに対して64の除算を行い、例えば:
int i = 20;

  int j = 30;

  i = i << 3;

  j = j >> 70;

結果は、まず余剰演算を行います.
3 % 32 //   3

  70 % 64 //   6

したがって,実際の右側の操作数は3と6であり,3と70ではない.
Javaプログラマーの90%が上記の観点を持っていますが、残念ながら、この考えは正しくありません.
右側のオペランドは、タイプ変数の値範囲を超えない限り、任意の整数値であってもよいことに注意してください.では、右側のオペランドが負の値の場合、たとえば次のようになります.
int i= 28;

  i = 28 << -4;

以上の観点から、まず32に対して余剰を求める.
 -4 % 32

結果はやはり−4で、今問題が来て、左に−4位を移動して、どのように移動しますか?
実際には、左側オペランドがintタイプの場合(リフティング後のintタイプを含む)、右側オペランドが5ビットだけ低いのが有効(5ビット低い範囲は0~31)であり、つまり、右側オペランドがマスク0 x 1 fと先に演算(&)し、その後、左側オペランドが対応するビット数だけ移動するものとみなすことができる.同様に、左側のオペランドがlongタイプの場合、右側のオペランドは6ビット低いだけが有効であり、右側のオペランドはマスク0 x 3 fと先に演算を行い、その後、対応するビット数を移動すると見なすことができる.たとえば、次のような代入演算があるとします.
 int i = 5 << -10;   −10     : 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 0

下位5位をとると、1 0 1 0という値が22、つまり次のようになります.
  int i = 5 << 22;

したがって,シフト演算右側の操作数を余剰演算と結びつけないのは正しくない.
シフト演算と乗算除算
データはバイナリで表されるため、左シフト1ビットは2を乗じることに相当し、右シフト1ビットは2を割ることに相当するという考えが一般的に存在するのは正しいのだろうか.次のプログラムで検証します.
【例】シフトと乗除
1. package chapter2;

  2.

  3. public class ShiftOperation {

  4. public static void main(String[] args) {

  5. testShift(10); //   

  6. testShift(-10); //   

  7. testShift(0); //0

  8. testShift(9); //   

  9. testShift(-9); //   

  10. }

  11.

  12. public static void testShift(int value) {

  13. int multiply = value * 2;

  14. int divide = value / 2;

  15. int leftShift = value << 1;

  16. int rightShift = value >> 1;

  17. System.out.println(value + "*2=" + multiply);

  18. System.out.println(value + "/2=" + divide);

  19. System.out.println(value + "<<1=" + leftShift);

  20. System.out.println(value + ">>1=" + rightShift);

  21. System.out.print("    :" + value + "*2="+ value + "<<1"); 22. if (multiply == leftShift) {

  23. System.out.println("  !");

  24. } else {

  25. System.out.println("   !");

  26. }

  27. System.out.print("    :" + value + "/2="+ value + ">>1"); 28. if (divide == rightShift) {

  29. System.out.println("  !");

  30. } else {

  31. System.out.println("   !");

  32. }

  33. }

  34.

本プログラムでは、いくつかの数値を選択し、その数値に2を乗じ、2を除算し、左シフト1位と右シフト1位の値(13~16行目)をそれぞれ算出し、次に2を乗じて左シフト1位(22~26行目)、2を除いて右シフト1位(28~32行目)と等しいかどうかを比較する.プログラムの実行結果は次のとおりです.
10*2=20   10/2=5   10<<1=20   10>>1=5       :10*2=10<<1  !       :10/2=10>>1  !   -10*2=-20   -10/2=-5   -10<<1=-20   -10>>1=-5       :-10*2=-10<<1  !       :-10/2=-10>>1  !   0*2=0   0/2=0   0<<1=0   0>>1=0       :0*2=0<<1  !       :0/2=0>>1  !   9*2=18   9/2=4   9<<1=18   9>>1=4       :9*2=9<<1  !       :9/2=9>>1  !   -9*2=-18   -9/2=-4   -9<<1=-18   -9>>1=-5       :-9*2=-9<<1  !       :-9/2=-9>>1   !

出力結果は最後の行を除いて等しい.では、最後の行には何か特別なものがありますか?これは、除去された丸めモードから説明します.Javaでは、両方のオペランドが整数である場合、結果も整数である(ArithmeticException異常が発生していないと仮定).割り切れない場合、結果は0に切り捨てられます.つまり、
0に近い方向に値をとります.たとえば、このプログラムでは、式:
 9 / 2

の値は4.5で、4から5の間にあり、4は0に近いため、0に丸められ、結果は4になります.これは下に丸められ、式:
 -9 / 2

の値は−4.5であり、−4と−5の間であり、0に向かって−4に切り込まれ、これは上方に切り込まれることに相当する.シフト演算では、式:
 9 >> 1

の値は4で、式は下に丸められます.
 -9 >> 1

の値が−5なのか、それとも切り下げに相当するのか、そのために異なる数値が現れる.乗算演算は、このような切り込みモードの問題には関与しないので、値は等しい.しかし,いったん除去できないと,丸めモードが関与し,結果に差が生じる.具体的な状況を表2〜4に示す.
したがって、左シフトnビットに2 nを乗じた値は等しく、2 nと右シフトnビットを除いた値も等しくなる.整除できない場合は、被除数が正数の場合、2 nと右シフトnビットを除いた値は等しく、被除数が負の場合、2 nと右シフトnビットを除いた値は等しくない.
以上の右シフトは、特別な説明がない場合は、記号付き右シフト(>>>)を指すことに注意してください.
作者:梁勇http://www.lemonpai.com/1009.html必ずこの出典を残してください.そうしないと、法律責任を追及します.
関連読書:ビッグデータJava基礎——シフト演算の真実剖析(二)http://www.lemonpai.com/1023.html質問があれば、皆さんによろしくお願いします.