1. 概要

Javaの黎明期から、すべての数値データ型が署名されています。 ただし、多くの場合、符号なしの値を使用する必要があります。 たとえば、イベントの発生数をカウントする場合、負の値に遭遇することは望ましくありません。

符号なし演算のサポートは、バージョン8の時点でようやくJDKの一部になりました。 このサポートはUnsignedIntegerAPIの形式で提供され、主にIntegerクラスとLongクラスの静的メソッドが含まれています。

このチュートリアルでは、このAPIについて説明し、符号なし数値を正しく使用する方法について説明します。

2. ビットレベルの表現

符号付きおよび符号なしの数値を処理する方法を理解するために、最初にビットレベルでのそれらの表現を見てみましょう。

Javaでは、数値は2の補数システムを使用してエンコードされます。このエンコードは、オペランドが符号付きか符号なしかにかかわらず、加算、減算、乗算など、多くの基本的な算術演算を実装します。

コード例を使用すると、状況がより明確になるはずです。 簡単にするために、byteプリミティブデータ型の変数を使用します。 short int long など、他の整数型の操作も同様です。

100の値を持つbyteタイプがあるとします。 この番号は、バイナリ表現0110_0100を持っています。

この値を2倍にしましょう:

byte b1 = 100;
byte b2 = (byte) (b1 << 1);

指定されたコードの左シフト演算子は、変数 b1 のすべてのビットを左の位置に移動し、技術的にはその値を2倍にします。 変数b2のバイナリ表現は、1100_1000になります。

符号なし型システムでは、この値は 2 ^ 7 + 2 ^ 6 + 2 ^3または200に相当する10進数を表します。 それにもかかわらず、符号付きシステムでは、左端のビットが符号ビットとして機能します。したがって、結果は -2 ^ 7 + 2 ^ 6 + 2 ^ 3 、または[ X139X]-56。

簡単なテストで結果を確認できます。

assertEquals(-56, b2);

符号付き数値と符号なし数値の計算は同じであることがわかります。 差異は、JVMが2進数表現を10進数として解釈する場合にのみ表示されます。

加算、減算、および乗算の操作は、JDKで変更を加えることなく、符号なしの数値で機能します。 比較や除算などの他の操作では、符号付き数値と符号なし数値の処理が異なります。

ここで、UnsignedIntegerAPIが役立ちます。

3. 符号なし整数API

Unsigned Integer APIは、Java8での符号なし整数演算のサポートを提供します。 このAPIのほとんどのメンバーは、IntegerおよびLongクラスの静的メソッドです。

これらのクラスのメソッドは同様に機能します。 したがって、 Integer クラスのみに焦点を当て、簡潔にするためにLongクラスは省略します。

3.1. 比較

Integer クラスは、 compareUnsigned という名前のメソッドを定義して、符号なしの数値を比較します。 このメソッドは、符号ビットの概念を無視して、すべてのバイナリ値を符号なしと見なします。

intデータ型の境界にある2つの数値から始めましょう。

int positive = Integer.MAX_VALUE;
int negative = Integer.MIN_VALUE;

これらの数値を符号付きの値として比較すると、ポジティブは明らかにネガティブよりも大きくなります。

int signedComparison = Integer.compare(positive, negative);
assertEquals(1, signedComparison);

数値を符号なしの値として比較する場合、左端のビットは符号ビットではなく最上位ビットと見なされます。 したがって、結果は異なり、ポジティブネガティブよりも小さくなります。

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. 除算とモジュロ

比較演算と同様に、符号なし除算およびモジュロ演算は、すべてのビットを値ビットとして処理します。したがって、これらの演算を符号付き数値と符号なし数値に対して実行すると、商と剰余は異なります。

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を解析する場合、text引数はMAX_VALUEより大きい数値を表すことができます。

このような大きな値は、 parseInt メソッドでは解析できません。このメソッドでは、MIN_VALUEからMAX_VALUEまでの数値のテキスト表現しか処理できません。

次のテストケースは、解析結果を検証します。

Throwable thrown = catchThrowable(() -> Integer.parseInt("2147483648"));
assertThat(thrown).isInstanceOf(NumberFormatException.class);

assertEquals(Integer.MAX_VALUE + 1, Integer.parseUnsignedInt("2147483648"));

parseUnsignedInt メソッドは、 MAX_VALUE より大きい数値を示す文字列を解析できますが、負の表現の解析に失敗することに注意してください。

3.4. フォーマット

解析と同様に、数値をフォーマットする場合、符号なし操作はすべてのビットを値ビットと見なします。 その結果、MAX_VALUEの約2倍の数のテキスト表現を生成できます。

次のテストケースは、符号付きと符号なしの両方の場合でMIN_VALUEのフォーマット結果を確認します。

String signedString = Integer.toString(Integer.MIN_VALUE);
assertEquals("-2147483648", signedString);

String unsignedString = Integer.toUnsignedString(Integer.MIN_VALUE);
assertEquals("2147483648", unsignedString);

4. 長所と短所

多くの開発者、特にCなどの符号なしデータ型をサポートする言語を使用している開発者は、符号なし算術演算の導入を歓迎しています。 ただし、これは必ずしも良いことではありません。

符号なし番号の需要には主に2つの理由があります。

まず、負の値が発生しない場合があり、unsigned型を使用すると、そもそもそのような値を防ぐことができます。 次に、符号なしタイプでは、符号付きの対応するタイプと比較して、使用可能な正の値の範囲を2倍にすることができます

符号なしの数のアピールの背後にある理論的根拠を分析しましょう。

変数が常に非負である必要がある場合、0未満の値は例外的な状況を示すのに便利です。

たとえば、 String.indexOf メソッドは、文字列内の特定の文字が最初に出現する位置を返します。 インデックス-1は、そのような文字がないことを簡単に示すことができます。

符号なし数値のもう1つの理由は、値空間の拡張です。 ただし、符号付きタイプの範囲が十分でない場合は、範囲を2倍にするだけで十分とは言えません。

データ型が十分に大きくない場合は、intBigInteger[の代わりにlongを使用するなど、はるかに大きな値をサポートする別のデータ型を使用する必要があります。 longではなくX177X]。

Unsigned Integer APIのもう1つの問題は、数値の2進形式が、符号付きか符号なしかに関係なく同じであるということです。 したがって、符号付きの値と符号なしの値を簡単に混在させることができ、予期しない結果が生じる可能性があります

5. 結論

Javaでの符号なし演算のサポートは、多くの人々の要求に応じて行われました。 ただし、それがもたらすメリットは明確ではありません。 この新機能を使用するときは、予期しない結果を避けるために注意が必要です。

いつものように、この記事のソースコードはGitHubから入手できます。