静大情報AC(IT) 6日目です。
前日(5日目)は欠員です。
前々日(4日目)はasdfg0280氏の「Nuxt.js使ってみた」です。
はじめに
JavaとJVMのSpecificationを引用する際には、それぞれ"Java§1.2.3"、"JVM§4.5.6"のように記述します。
Java/JVMのバージョンは11とします。
#Unsigned right shift operator?
Javaにはunsigned right shift operator(unsigned右シフト演算子)なる演算子があります。
int i = -4 >> 1; // -2 (signed right shift)
int j = -4 >>> 1; // 2147483646 (unsigned right shift)
通常のシフトが符号拡張により符号を保存する(算術シフト)のに対し、unsigned右シフトにおいては符号ビットを気にせずそのままシフト(論理シフト)します。(Java§15.19)
C/C++にはないこの演算子、なぜJavaにはあるのでしょうか。
#Javaにはunsigned数値型がない
Java§4.2.1によれば、Javaの組み込み整数型は以下の4つです。
-
byte
(-128 ~ 127) -
short
(-32768 ~ 32767) -
int
(-2147483648 ~ 2147483647) -
long
(-9223372036854775808 ~ 9223372036854775807) -
char
('\u0000'
~'\uffff'
、すなわち0 ~ 65535)
値の範囲からわかるようにchar
以外はすべてsignedです。(JVM§2.3にもchar
のみがunsignedとの記述があります)
また、char
でさえもシフト演算時には内部的にint
に変換されてから演算に使われます。("Unary numeric promotion", Java§5.6.1)
JVM§2.11.1によればJVMのシフト演算命令はint
とlong
にしか定義されていないことから、シフト演算に限って言えば、Javaにはunsigned整数型はないと言えます。
Unsignedなシフトの必要性
さて、unsigned整数型がないとどのように困るのでしょうか。
次のコードを見てみましょう。
String toBits(int val) {
var builder = new StringBuilder();
for(int j = 0; j < Integer.SIZE; j++, val >>>= 1) {
builder.append((val & 1) == 1 ? "1" : "0");
}
return builder.reverse().toString();
}
これは、int
型引数を取りそのビット表現をString
で返す関数です。
例:
-1
-> "11111111111111111111111111111111"
20181206
-> "00000001001100111111000011010110"
3行目で>>>=
(>>>
しつつ代入する演算子)を使っています。
仮にJavaの世界に>>>
がなくても、この関数が書けないというわけではありません。
ただ、負の値を算術右シフトすると符号拡張で上位ビットを1が埋めてしまうので、負の数に対しては特別な操作が必要となります。
このように、Javaにおいてはunsigned right shift operatorはなくても死にはしないけれど、あったほうが便利だということがわかりました。
C/C++にはなぜright shift operatorがないか
C/C++では符号なし整数型があり、これらの型に対するシフトは論理シフトになることが規格で定められています。
したがって、right shift operatorがなくても同様の操作を行うことができます。
おわり
翌日(7日目)は欠員です。
翌々日(8日目)はhi97_ia16氏の「Node.jsで大学の成績を自動取得するシステムを開発した話」です。
(両脇に人がいない……)