title: Java Integer.toBinaryString() メソッドのソースコードと原理解析 (基数変換、ビット操作)
date: 2022-12-27 17:31:38
タグ:
- Java の
カテゴリ: - Java
カバー: https://cover.png
機能: false
1. 使い方とソースコードの概要
Integer.toBinaryString()
このメソッドは、次の例のように、10 進数の整数を 2 進数に変換するために使用されます。
完全なソース コード呼び出しは次のとおりです。
public static String toBinaryString(int i) {
return toUnsignedString0(i, 1);
}
private static String toUnsignedString0(int val, int shift) {
// assert shift > 0 && shift <=5 : "Illegal shift value";
int mag = Integer.SIZE - Integer.numberOfLeadingZeros(val);
int chars = Math.max(((mag + (shift - 1)) / shift), 1);
char[] buf = new char[chars];
formatUnsignedInt(val, shift, buf, 0, chars);
// Use special constructor which takes over "buf".
return new String(buf, true);
}
public static int numberOfLeadingZeros(int i) {
// HD, Figure 5-6
if (i == 0)
return 32;
int n = 1;
if (i >>> 16 == 0) {
n += 16; i <<= 16; }
if (i >>> 24 == 0) {
n += 8; i <<= 8; }
if (i >>> 28 == 0) {
n += 4; i <<= 4; }
if (i >>> 30 == 0) {
n += 2; i <<= 2; }
n -= i >>> 31;
return n;
}
static int formatUnsignedInt(int val, int shift, char[] buf, int offset, int len) {
int charPos = len;
int radix = 1 << shift;
int mask = radix - 1;
do {
buf[offset + --charPos] = Integer.digits[val & mask];
val >>>= shift;
} while (val != 0 && charPos > 0);
return charPos;
}
final static char[] digits = {
'0' , '1' , '2' , '3' , '4' , '5' ,
'6' , '7' , '8' , '9' , 'a' , 'b' ,
'c' , 'd' , 'e' , 'f' , 'g' , 'h' ,
'i' , 'j' , 'k' , 'l' , 'm' , 'n' ,
'o' , 'p' , 'q' , 'r' , 's' , 't' ,
'u' , 'v' , 'w' , 'x' , 'y' , 'z'
};
2.分析
2.1 バイナリ変換
まず、10進数から2進数への変換方法を演算の論理から理解します.大まかに2つの方法があります.ここではデモンストレーションのために8ビットのみを使用しています.
1.ショートディビジョン
基本的に、商が0になるまで2で割り続け、剰余を逆順に出力します。例: 15、16
2| 15 2| 16
———— ————
2| 7 1 ^ 2| 8 0 ^
———— | ———— |
2| 3 1 | 2| 4 0 |
———— | ———— |
2| 1 1 | 2| 2 0 |
———— | ———— |
0 1 | 2| 1 0 |
———— |
0 1 |
以上より、15 の 2 進数表現は 0000 1111 であり、16 の 2 進数表現は 0001 0000 です。
2.加重加算方式
つまり、2 進数は、最初に重み付き係数展開として書き込まれ、2 進数ビットに対応してから、10 進数の加算規則に従って合計されます。
2 の 0 乗は 1 -------------- 1 桁目に対応
2 の 1 乗は 2 -------------- 対応2 の 2乗は
4 -------------- 3 桁目に対応する
2 の 3 乗は 8 ------------- - 4 桁目に対応する
2 の 4 乗は 16 -------------- 5 桁目に対応する
2 の 5 乗は 32 ----------- --- 6 桁目に対応する
2 の 6 乗は 64 -------------- 7 桁目に対応する
...
例: 15 = 2^3 + 2^2 + 2^1 + 2^0、16 = 2^4、つまり:
15 16
0000 0000 0000 0000
0000 1000 0001 0000
0000 1100
0000 1110
0000 1111
2.2 元コード、逆コード、補完コード
次に、元のコード、逆コード、補数コードの関連知識について学び、デモンストレーションには 8 ビットのみを使用します
1. オリジナルコード
元のコード、つまり 2.1 で示したバイナリへの変換。たとえば、15、元のコードは 0000 1111 です。2.1では正数のみを例に挙げましたが、ここでは正数と負数を区別し、同時に符号ビットの概念を導入する必要があります。バイナリの最初のビットは符号ビットです。正の数は 0、負の数は 1 です。符号ビットはビットの変換と操作には関与しません。
例: -15、元のコードは 1000 1111
15 オリジナルコード: 0000 1111
-15 オリジナルコード: 1000 1111
2.逆コード
逆コード、つまり、元のコードがビットごとに逆になります。ここで、正数の逆符号は元の符号と同じであることに注意してください。例: -15、元のコードは 1000 1111、符号ビットは変換に関与せず、ビットごとの反転は 1111 0000 です。
15 逆コード: 0000 1111、元のコードと同じ
-15 オリジナルコード: 1000 1111
-15 インバース コード: 1111 0000
3.補完コード
補数コード、つまり、逆コードに 1 を追加します。完全なステートメントは、元のコードを逆にして 1 を追加する必要があります。また、正数の補数は元と同じであることに注意してください。例: -15、逆コードは 1111 0000、プラス 1 は 1111 0001
15 の補数コード: 0000 1111、元のコードと同じ
-15 元コード: 1000 1111
-15 逆コード: 1111 0000
-15 補コード: 1111 0001
正数の原符号、逆符号、補数符号は同じであり、正数の 2 進数表現は 2 進数の原符号として表すことができます (実際には、これも補数符号である必要はありません)。計算されます)。負の数の 2 進数表現は 2 の補数であり、記事の最初の写真に示されている例は次のとおりです。int は 32 ビットであるため、負の数は 1 の束を表示し、正の数は前の 0 を削除します
2.3 ビット演算子
ビット操作について詳しく学び、8 ビットのみを使用してデモを行いましょう。
1. <<
: ビット単位の左シフト演算子
変換された 2 進数を指定された桁数だけ左にシフトします。たとえば、15 << 2、つまり 0000 1111 << 2、つまり 0011 1100 であり、10 進表現は 60 です。ここでは後ろが0で埋められています
0000 1111
0011 1100
注: ここでは 1 << n を使用して 2 の n 乗を表すことができます。これは、実際には、1 ビットの左シフトごとに 2 を乗算することと同等であるためです。
2. >>
: ビットごとの右シフト演算子
変換された 2 進数を指定された桁数だけ右にシフトします。たとえば、15 >> 2、つまり 0000 1111 >> 2、つまり 0000 0011 で、10 進表現は 3 です。ここで、前の補数は符号付き、正の数、符号ビットが 0、次に補数 0、負の数、符号ビットが 1、次に補数 1 です。
0000 1111
0000 0011
右にずらすことは仮に 2 で割ることと見なすことができますが、このとき偶数と奇数があります。偶数、正の数、負の数の値は同じであり、ここで言及されている値は符号なしでそれ自体の値を指し、奇数の場合、負の数の値は正の数よりも 1 大きくなります
3. >>>
: ビット単位の右シフト ゼロ パディング演算子
変換されたバイナリを指定されたビット数だけ右にシフトし、シフトされた位置をゼロで埋めます。符号ビットはここでは区別されないので, 符号なし右シフトとも呼ばれます. 正の数は元の値が 0 であるため効果がなく, 負の数は元の値を変更します. 例: -15 >>> 2, その1111 0001 >>> 2. 0011 1100 です。ここでは 8 ビットのみをデモンストレーションに使用し、完全な 32 ビットを下の図に示します。前の 0 は省略されています。は元より 2 ビット少ない:
1111 0001
0011 1100
4. &
: 対応するビットがすべて 1 の場合、結果は 1 です。それ以外の場合は 0 です。例: 15 & 16、3 & 7
15: 0000 1111 3: 0000 0011
16: 0001 0000 7: 0000 0111
0000 0000 0000 0011
15 & 16 の結果は 0、3 & 7 の結果は 3 です。
2.4 ソースコード分析
1. まず、呼び出された最上位のメソッドを見てください。ここで、toUnsignedString0()
メソッド。パラメータ i は、変換するために渡す値です。ここでの 1 は桁数を示し、1 は 2 進数を意味し、3 は 2 進数を意味します。は 8 進数、4 は 16 進数を意味します
public static String toBinaryString(int i) {
return toUnsignedString0(i, 1);
}
public static String toOctalString(int i) {
return toUnsignedString0(i, 3);
}
public static String toHexString(int i) {
return toUnsignedString0(i, 4);
}
2. もう一度toUnsignedString0()
メソッドInteger.numberOfLeadingZeros()
. ここでまずメソッドを呼び出します. このメソッドは主に 2 進表現で上位連続する 0 ビットの数を計算し、これを Integer.SIZE (32) で減算して計算します.表現される文字配列の長さ。これは、先行する 0 を省略したものと理解できます。例えば:
15: 0000 0000 0000 0000 0000 0000 0000 1111、元の表現
15: 1111、実際の表現
このステップは、前の 0 を省略し、表現する必要がある桁数のみを保持するものとして理解できます。
private static String toUnsignedString0(int val, int shift) {
// assert shift > 0 && shift <=5 : "Illegal shift value";
int mag = Integer.SIZE - Integer.numberOfLeadingZeros(val);
int chars = Math.max(((mag + (shift - 1)) / shift), 1);
char[] buf = new char[chars];
formatUnsignedInt(val, shift, buf, 0, chars);
// Use special constructor which takes over "buf".
return new String(buf, true);
}
@Native public static final int SIZE = 32;
3. 次に、Integer.numberOfLeadingZeros()
の。ここでは、判断のために複数の間隔 [16、24、28、30] に分割される単純な二分法が使用されます。ここにはまだ間隔 [30, 32] がありますが、なぜカウントされないのですか? 以下の手順 6 を参照してください。ここで、n は上位連続 0 の数を表します
- まずiが0かどうか判断し、0なら上位32ビット連続0、そのまま32を返す
- 次に、i >>> 16 が 0 であるかどうかを判断します.これは、最初に区間の半分を判断することと理解できます.0 の場合は、少なくとも上位 16 個の連続した 0 が含まれていることを意味し、n は 16 を追加し、次に i < <= 16、その後の判断のために i 16 ビット 0 を削除
- i >>> 24 が 0 かどうか、つまり上位 8 個以上連続する 0 かどうかを判断し、0 なら n に 8 を足し、i <<= 8 から 0 の 8 ビットを取り除く私はその後の判断を下す
- 同上、上位4連続0以上か判定する
- 上記のように、上位連続した 0 が 2 つ以上含まれているかどうかを判断し、ここではすでに i >>> 30
- 最後に、やはり[30,32]、長さ2の区間、[00,01,10,11]の4通りあり、すでに0と判断したので、00は除外は[01,10,11]であり、x1であれば最上位の連続する0ビットのみを判断すればよいのでxの数を判断する必要はなく、x0であれば00は除外されているので、 x は 1 です。したがって、実際には 1 ビットだけを判断すればよいので、区間 [30, 32] はカウントされません. 31 に達した場合にのみ、最初に n の初期値が 1 に割り当てられます
.はデフォルトで 0 であり、n -= i >>> 31
このビットが何であるかを判断するために再度渡されます。0 の場合、n = n - 0、変更されません。1 の場合、n = n - 1 から元のデフォルトの初期値 1 を引いたものです。 . 実際、この手法はif (i >>> 31 == 0) { n += 1; }
の判断を。2 番目の書き方は次のとおりです。
public static int numberOfLeadingZeros(int i) {
// HD, Figure 5-6
if (i == 0)
return 32;
int n = 1;
if (i >>> 16 == 0) {
n += 16; i <<= 16; }
if (i >>> 24 == 0) {
n += 8; i <<= 8; }
if (i >>> 28 == 0) {
n += 4; i <<= 4; }
if (i >>> 30 == 0) {
n += 2; i <<= 2; }
n -= i >>> 31;
return n;
}
public static int numberOfLeadingZeros(int i) {
// HD, Figure 5-6
if (i == 0)
return 32;
int n = 0;
if (i >>> 16 == 0) {
n += 16; i <<= 16; }
if (i >>> 24 == 0) {
n += 8; i <<= 8; }
if (i >>> 28 == 0) {
n += 4; i <<= 4; }
if (i >>> 30 == 0) {
n += 2; i <<= 2; }
if (i >>> 31 == 0) {
n += 1; }
return n;
}
4.toUnsignedString0()
この、numberOfLeadingZeros()
を呼び出して上位の連続する 0 の数を取得し、この数を Integer.SIZE から減算して、表現する桁数を取得します。
次に、2/8/16 基数システムに対応する文字配列の長さを計算するMath.max(((mag + (shift - 1)) / shift), 1);
ために. 上記のパラメータ シフトは、基数を示すために使用されます.1 は 2 進法を意味し、3 は 8 進法を意味し、4 は 16 進法を意味します.
文字配列の長さを取得した後、対応する文字配列を作成し、配列を埋めるformatUnsignedInt()
ために
private static String toUnsignedString0(int val, int shift) {
// assert shift > 0 && shift <=5 : "Illegal shift value";
int mag = Integer.SIZE - Integer.numberOfLeadingZeros(val);
int chars = Math.max(((mag + (shift - 1)) / shift), 1);
char[] buf = new char[chars];
formatUnsignedInt(val, shift, buf, 0, chars);
// Use special constructor which takes over "buf".
return new String(buf, true);
}
5.formatUnsignedInt()
メソッドは次のとおりです. パラメータ val は変換される値です. shift は桁数を示します.ここでは 1 です. buf は作成された文字配列です. offset はオフセットです.ここでは 0 です. len は長さです.配列。その中で、6行目は、すべての数字と文字を含む定義済みの文字配列Integer.digits
を使用しています
ここでは、実際に対応する桁数に従って入力しています
-
配列の長さをcharPosに代入し、charPosを使用して計算します
-
radix = 1 << shift
、 前述のように、1 << n は実際には 2 の n 乗です。ここでは基数を表すために使用され、2 進数は 2 の 1 乗、8 進数は 2 の 3 乗、16 進数は 2 の 4 乗です -
マスクは基数から 1 を引いたもので、これは val を使用した後続の & 演算に使用され、実際には基数に対応する桁数を 1 つずつ一致させます。例: shift は 3、つまり 8 進数で、mask は 7 です。
8 == 基数 = 1 << 3;
7 == マスク = 基数 - 1;& 演算にマスクを使用すると、111 として表されます。つまり、2 進数の 3 ビットが 8 進数の 1 ビットを表します。
-
val & mask 値を取得したら, digits 配列で対応するインデックス文字を見つけて, 作成した文字配列である buf に代入します. 桁数の変更に対応して逆順で格納されることに注意してください.右から左へ
-
次に、val が対応する桁数だけ右にシフトされ、サイクルが一致します。
-
最後に、塗りつぶされた文字配列を返します
static int formatUnsignedInt(int val, int shift, char[] buf, int offset, int len) {
int charPos = len;
int radix = 1 << shift;
int mask = radix - 1;
do {
buf[offset + --charPos] = Integer.digits[val & mask];
val >>>= shift;
} while (val != 0 && charPos > 0);
return charPos;
}
final static char[] digits = {
'0' , '1' , '2' , '3' , '4' , '5' ,
'6' , '7' , '8' , '9' , 'a' , 'b' ,
'c' , 'd' , 'e' , 'f' , 'g' , 'h' ,
'i' , 'j' , 'k' , 'l' , 'm' , 'n' ,
'o' , 'p' , 'q' , 'r' , 's' , 't' ,
'u' , 'v' , 'w' , 'x' , 'y' , 'z'
};
6.toUnsignedString0()
メソッド。最後のステップは、文字配列を文字列に変換して返すことであり、プロセス全体がここで終了します。
private static String toUnsignedString0(int val, int shift) {
// assert shift > 0 && shift <=5 : "Illegal shift value";
int mag = Integer.SIZE - Integer.numberOfLeadingZeros(val);
int chars = Math.max(((mag + (shift - 1)) / shift), 1);
char[] buf = new char[chars];
formatUnsignedInt(val, shift, buf, 0, chars);
// Use special constructor which takes over "buf".
return new String(buf, true);
}