Java 8符号なし算術サポート
1. 概要
Javaのd明期から、すべての数値データ型は署名されています。 ただし、多くの場合、符号なしの値を使用する必要があります。 たとえば、イベントの発生数を数える場合、負の値に遭遇することは望ましくありません。
バージョン8では、符号なし算術のサポートがついにJDKの一部になりました。 This support came in the form of the Unsigned Integer API, primarily containing static methods in the Integer and Long classes.
このチュートリアルでは、このAPIについて説明し、符号なし数値を正しく使用する方法について説明します。
2. ビットレベルの表現
符号付きおよび符号なしの数値を処理する方法を理解するために、最初にビットレベルでのそれらの表現を見てみましょう。
In Java, numbers are encoded using the two’s complement system.このエンコーディングは、オペランドが符号付きか符号なしかに関係なく、加算、減算、乗算など、多くの基本的な算術演算を同じ方法で実装します。
コード例を使用すると、状況がより明確になるはずです。 簡単にするために、byteプリミティブデータ型の変数を使用します。 操作は、short、int、またはlongなどの他の整数数値タイプでも同様です。
値が100のタイプbyteがあるとします。 この数値の2進表現は0110_0100です。
この値を2倍にしましょう:
byte b1 = 100;
byte b2 = (byte) (b1 << 1);
指定されたコードの左シフト演算子は、変数b1のすべてのビットを左の位置に移動し、技術的にはその値を2倍にします。 変数b2のバイナリ表現は1100_1000になります。
符号なし型システムでは、この値は2^7 + 2^6 + 2^3または200に相当する10進数を表します。 それにもかかわらず、in a signed system, the left-most bit works as the sign bit.したがって、結果は -2^7 + 2^6 + 2^3、または-56になります。
簡単なテストで結果を確認できます:
assertEquals(-56, b2);
符号付き数値と符号なし数値の計算は同じであることがわかります。 Differences only appear when the JVM interprets a binary representation as a decimal number.
加算、減算、および乗算の演算は、JDKでの変更を必要とせずに、符号なしの数値で機能します。 比較や除算などの他の操作では、符号付き数値と符号なし数値を別々に処理します。
ここで、符号なし整数APIが役立ちます。
3. 符号なし整数API
符号なし整数APIは、Java 8で符号なし整数演算をサポートします。 このAPIのほとんどのメンバーは、IntegerクラスとLongクラスの静的メソッドです。
これらのクラスのメソッドも同様に機能します。 したがって、簡潔にするためにLongクラスは省略して、Integerクラスのみに焦点を当てます。
3.1. 比較
Integerクラスは、符号なし数値を比較するためのcompareUnsignedという名前のメソッドを定義します。 This method considers all binary values unsigned, ignoring the notion of the sign bit.
intデータ型の境界にある2つの数値から始めましょう。
int positive = Integer.MAX_VALUE;
int negative = Integer.MIN_VALUE;
これらの数値を符号付きの値として比較すると、positiveは明らかにnegativeよりも大きくなります。
int signedComparison = Integer.compare(positive, negative);
assertEquals(1, signedComparison);
数値を符号なしの値として比較する場合、左端のビットが符号ビットではなく最上位ビットと見なされます。 したがって、結果は異なり、positiveはnegativeよりも小さくなります。
int unsignedComparison = Integer.compareUnsigned(positive, negative);
assertEquals(-1, unsignedComparison);
これらの数値のバイナリ表現を見ると、より明確になるはずです。
-
MAX_VALUE→0111_1111…1111
-
MIN_VALUE→1000_0000…0000
左端のビットが通常の値のビットである場合、MIN_VALUEはバイナリシステムのMAX_VALUEよりも1単位大きくなります。 このテストにより、次のことが確認されます。
assertEquals(negative, positive + 1);
3.2. 除算とモジュロ
したがって、比較演算と同様に、the unsigned division and modulo operations process all bits as value bits.は、符号付き数値と符号なし数値に対してこれらの演算を実行すると、商と剰余が異なります。
int positive = Integer.MAX_VALUE;
int negative = Integer.MIN_VALUE;
assertEquals(-1, negative / positive);
assertEquals(1, Integer.divideUnsigned(negative, positive));
assertEquals(-1, negative % positive);
assertEquals(1, Integer.remainderUnsigned(negative, positive));
3.3. 構文解析
parseUnsignedIntメソッドを使用してStringを解析する場合、the text argument can represent a number greater than MAX_VALUE.
このような大きな値は、MIN_VALUEからMAX_VALUEまでの数値のテキスト表現のみを処理できるparseIntメソッドでは解析できません。
次のテストケースでは、解析結果を検証します。
Throwable thrown = catchThrowable(() -> Integer.parseInt("2147483648"));
assertThat(thrown).isInstanceOf(NumberFormatException.class);
assertEquals(Integer.MAX_VALUE + 1, Integer.parseUnsignedInt("2147483648"));
parseUnsignedIntメソッドは、MAX_VALUEより大きい数値を示す文字列を解析できますが、負の表現の解析に失敗することに注意してください。
3.4. フォーマット
解析と同様に、数値をフォーマットするとき、符号なしの操作はすべてのビットを値ビットと見なします。 したがって、we can produce the textual representation of a number about twice as large as MAX_VALUE.
次のテストケースでは、符号付きと符号なしの両方のケースでMIN_VALUEのフォーマット結果を確認しています。
String signedString = Integer.toString(Integer.MIN_VALUE);
assertEquals("-2147483648", signedString);
String unsignedString = Integer.toUnsignedString(Integer.MIN_VALUE);
assertEquals("2147483648", unsignedString);
4. 長所と短所
多くの開発者、特にCなどの符号なしデータ型をサポートする言語の開発者は、符号なし算術演算の導入を歓迎します。 ただし、this isn’t necessarily a good thing.
符号なしの数値を要求する主な理由は2つあります。
まず、負の値が決して発生しない場合があり、符号なしの型を使用すると、そもそもそのような値を防ぐことができます。 次に、符号なしの型では、符号付きの型と比較してdouble the range of usable positive valuesを実行できます。
符号なしの数字に対するアピールの背後にある理論的根拠を分析しましょう。
変数が常に非負である必要がある場合、0未満の値は、例外的な状況を示すのに便利な場合があります。
たとえば、String.indexOfメソッドは、文字列内の特定の文字が最初に出現する位置を返します。 インデックス-1は、そのような文字がないことを簡単に示すことができます。
符号なしの数値の他の理由は、値空間の拡大です。 ただし、if the range of a signed type isn’t enough, it’s unlikely that a doubled range would suffice.
データ型が十分に大きくない場合は、intの代わりにlongを使用する、BigIntegerの代わりにBigIntegerを使用するなど、はるかに大きな値をサポートする別のデータ型を使用する必要があります。 t3)s。
Unsigned Integer APIのもう1つの問題は、数値の2進形式が、符号付きか符号なしかに関係なく同じであるということです。 したがって、easy to mix signed and unsigned values, which may lead to unexpected resultsです。
5. 結論
Javaでの符号なし算術のサポートは、多くの人々の要求に応じています。 ただし、それがもたらす利点は不明です。 この新しい機能を使用するときは、予期しない結果を避けるために注意が必要です。
いつものように、この記事のソースコードはover on GitHubで入手できます。