Java Integer.toBinaryString()メソッドのソースコードと原理解析(基数変換、ビット演算)


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 の数を表します

  1. まずiが0かどうか判断し、0なら上位32ビット連続0、そのまま32を返す
  2. 次に、i >>> 16 が 0 であるかどうかを判断します.これは、最初に区間の半分を判断することと理解できます.0 の場合は、少なくとも上位 16 個の連続した 0 が含まれていることを意味し、n は 16 を追加し、次に i < <= 16、その後の判断のために i 16 ビット 0 を削除
  3. i >>> 24 が 0 かどうか、つまり上位 8 個以上連続する 0 かどうかを判断し、0 なら n に 8 を足し、i <<= 8 から 0 の 8 ビットを取り除く私はその後の判断を下す
  4. 同上、上位4連続0以上か判定する
  5. 上記のように、上位連続した 0 が 2 つ以上含まれているかどうかを判断し、ここではすでに i >>> 30
  6. 最後に、やはり[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を使用しています

ここでは、実際に対応する桁数に従って入力しています

  1. 配列の長さをcharPosに代入し、charPosを使用して計算します

  2. radix = 1 << shift、 前述のように、1 << n は実際には 2 の n 乗です。ここでは基数を表すために使用され、2 進数は 2 の 1 乗、8 進数は 2 の 3 乗、16 進数は 2 の 4 乗です

  3. マスクは基数から 1 を引いたもので、これは val を使用した後続の & 演算に使用され、実際には基数に対応する桁数を 1 つずつ一致させます。例: shift は 3、つまり 8 進数で、mask は 7 です。

    8 == 基数 = 1 << 3;
    7 == マスク = 基数 - 1;

    & 演算にマスクを使用すると、111 として表されます。つまり、2 進数の 3 ビットが 8 進数の 1 ビットを表します。

  4. val & mask 値を取得したら, digits 配列で対応するインデックス文字を見つけて, 作成した文字配列である buf に代入します. 桁数の変更に対応して逆順で格納されることに注意してください.右から左へ

  5. 次に、val が対応する桁数だけ右にシフトされ、サイクルが一致します。

  6. 最後に、塗りつぶされた文字配列を返します

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);
}

おすすめ

転載: blog.csdn.net/ACE_U_005A/article/details/128456243