物事の賛否両論は、何千年もの間哲学者によって議論されてきました。コンピューターの0と1も、いつものようにさまざまなトリックをしました。
バイナリ番号VS10進数
このセクションでは、バイナリ表記と10進数への変換方法について説明します。これらの内容に既に精通している場合は、次のセクションにスキップできます。
私たちは小数の世界に住んでいます。10ダイムはワンピースで、10ワンツーはキャティです。数学では、完全な小数または借用したものがあります。
10進数の底は0から9までなので、すべての10進数は0から9までの9つの数字で構成されます。
コンピューターの最下層は2進数を扱います。10進数を比較して、2進数の特性を確認できます。
完全な2つは1つを入力するか、1つを2つとして借用します。ベースは0と1です。つまり、すべての2進数は2つの数値0と1で構成されます。
10のシステムに関しては、10の1が「フル10」の状態になっているため、「1を進める」必要があります。つまり、10は10であり、10の1が組み合わされているため、その値は10になります。
バイナリに関しては、2つの1が「フル2」の状態になっているため、「1を進める」必要があります。つまり、2つの1が結合されているため、2の数値である10であり、その値は2です。
これを10進数と2進数の特性と組み合わせて理解すれば、非常に簡単に理解できます。
1 + 1 = 2-> 10。
1 + 1 + 1 = 3 = 2 + 1-> 10 + 1-> 11。
1 + 1 + 1 + 1 = 4 = 3 + 1-> 11 + 1-> 100。
類推により、いくつかの10進数と対応するバイナリをリストします。
0-> 000
1-> 001
2-> 010
3-> 011
4-> 100
5-> 101
次に、2進数と10進数の変換関係を見つけてみてください。
まず、10進数は各位置の数字でどのように表されますか?誰もがおなじみだと思います。次の例のように:
123-> 100 + 20 + 3
123-> 1100 + 2 10 + 3 * 1
10のシステムは1の小数でいっぱいなので、10と接続する方法を見つけたい場合、100は10の2乗、10は10の1乗、1は10の0乗です。
123-> 1 10 ^ 2 + 2 10 ^ 1 + 3 * 10 ^ 0;
さらに、百の位の位置は3ですが、累乗は2、つまり正確に3マイナス1であり、十の位置は2ですが、累乗は1、つまり正確に2マイナス1であり、1の位は1マイナスです。 0乗である1に移動します。
だから、この式が出てきました、それは単純すぎます、誰もがそれを知っているので、私はそれを書きません。
次に、この「ルーチン」を2進数で試してみましょう。ただし、2進数は2対1でいっぱいなので、2の累乗を使用します。
000-> 0 2 ^ 2 + 0 2 ^ 1 + 0 2 ^ 0
000-> 0 4 + 0 2 + 0 1-> 0
000-> 0
001-> 0 2 ^ 2 + 0 2 ^ 1 + 1 2 ^ 0
001-> 0 4 + 0 2 + 1 1-> 1
001-> 1
010-> 0 2 ^ 2 + 1 2 ^ 1 + 0 2 ^ 0
010-> 0 4 + 1 2 + 0 1-> 2
010-> 2
011-> 0 2 ^ 2 + 1 2 ^ 1 + 1 2 ^ 0
011-> 0 4 + 1 2 + 1 1-> 3
011-> 3
100-> 1 2 ^ 2 + 0 2 ^ 1 + 0 2 ^ 0100-
> 1 4 + 0 2 + 0 1-
> 4100-> 4
101-> 1 2 ^ 2 + 0 2 ^ 1 + 1 2 ^ 0
101-> 1 4 + 0 2 + 1 1-> 5
101-> 5
計算された数値は、対応する10進数であることがわかりました。これは偶然ですか?もちろん違います。実際には:
これは、2進数を10進数に変換する方法です。
また、数学を模倣して次の式を導き出すこともできます。
d = b(n)+ b(n-1)+ ... + b(1)+ b(0)
b(n)= a * 2 ^ n、(a = {0,1}、n> = 0)
これは、2進数の各ビットを10進数に変換し、それらを合計することです。
負のバイナリVS正のバイナリ
前のセクションでは、例として正の数を使用しました。正の数に加えて、負の数とゼロがあります。
したがって、コンピュータ業界では、正と負を考慮する必要がある場合、バイナリの最上位ビットは符号ビットであると規定されています。
つまり、この位置の0または1は、数値を計算するためではなく、数値記号を表すために使用され、次のように規定されています。
0は正の数、1は負の数です。
その0は正でも負でもありませんが、どのように表現する必要がありますか?0のバイナリを出力します。
0-> 00000000
すべてが0であり、最上位ビットも0であることがわかっているため、0は特殊なケースです。
次に、負の数のバイナリ表現について説明します。これを読んだ後、「突然の実現」の感覚があることを確認します(そうでない場合は、仕方がありません)、ハハ。
長い間数学の影響を受けて、正の数を対応する負の数に変えるには、前に負の記号「-」を追加するだけです。
これに基づいて、コンピュータの世界における上記の規制と組み合わせると、次のように、最上位ビットが0から1に設定されている限り、正の数が対応する負の数になると簡単に推測できます。
1のバイナリは00000001であるため
したがって、-1のバイナリは10000001です。
これが間違っていることを厳粛に宣言します。理由を知るために読んでください。
まず、公式の観点から(bのふりをして)正しい結果が得られ、次に個人の観点から(突然の実現のために)正しい結果が得られます。
公式(または学術)の観点から、最初に3つの概念を紹介します。
元のコード:数値を正の数値として扱い(負の場合は負の符号を削除します)、そのバイナリ表現は元のコードと呼ばれます。
逆コード:元のコードの0を1に、1を0に変換し(つまり、0と1を交換し)、その結果を逆コードと呼びます。
補完コード:逆コードに1を加算すると、その結果は補完コードと呼ばれます。
(これは学界の用語です。理由は気にしないでください。覚えておいてください)
次の導出を行うための例として-1を取り上げます。
-1を1として扱い、元のコードは00000001です。
0と1を逆にすると、逆のコードは11111110になります。
次に1を加算すると、補数は11111111になります。
したがって、-1の補数は11111111です。次に、クラスライブラリのツールクラスを使用して-1のバイナリ形式を出力し、それがまだそれであることを確認します。これは偶然ではありません。理由は次のとおりです。
コンピューターでは、負の数のバイナリはその補数で表されます。
これは公式の言い方です、私はいつもみんなをいくつかの名詞と混同するのが好きです。
個人的な観点から、最も「地震」の方法で秘密を明らかにしましょう。
まず、-1のバイナリ形式は11111111です。これは、一度に受け入れるのは本当に簡単ではありません。
それどころか、それに対応する1のバイナリは00000001であるため、-1のバイナリは10000001であると想定する方が受け入れられます。
このように、値(つまり絶対値)の大きさの観点からはすべて1であり、象徴的な観点からは1つは1で、もう1つは0です。これは、1つの負と1つの正を意味し、単に「完全」です。
では、なぜこの仮定の形式が間違っているのでしょうか。
なぜなら、10進数の観点からは、1 +(-1)= 0です。
次に、それらを仮説の形式で対応するバイナリに変換します。
00000001 + 10000001 = 10000010、
仮定によれば、この結果の値は-2です。
1つが0で、もう1つが-2であることがわかりますが、これは明らかに間違っています。異なるベースが使用されますが、結果は同じであるはずです。
明らかに、バイナリ計算方法の結果は間違っています。エラーの理由は、-1のバイナリ形式を想定した方法で実行できないためです。
-1のバイナリ値を計算するにはどのロジックを使用する必要がありますか?あなたはそれを推測したと思います。
なぜなら、-1 = 0-1なので、
-1 = 00000000-00000001 = 11111111。
したがって、-1のバイナリは11111111です。したがって、
-1 + 1 = 11111111 + 00000001 = 00000000 = 0。
これは、-1バイナリがすべて1である理由をすぐに理解しますか?このフォームは数値計算のニーズを満たすためです。
同様に、-2のバイナリを計算できます。
-2 = -1--1 = 11111111-00000001 = 11111110。
実際、元のコード/逆コード/補完コード間の変換関係も、正と負の数の合計がゼロであることに基づいて設計されています。注意深い経験の後、あなたは理解することができます。
日当たりの良い白い雪と白鳥のリバがあることを除けば、公式の視点と個人の視点の本質は同じであることがわかります。
これは私に優雅さと下品さを思い出させます。多くの人々は優雅さの追求を宣伝します、しかし彼らが必要とするのは下品さです。
正の数と対応する負の数の例を次に示します。
2,00000010
-2,11111110
5,00000101
-5,11111011
127、01111111
-127、10000001
10進数の合計が0であり、対応する2進数の合計も0であることがわかります。
これは負の数の正しいバイナリ表現ですが、見た目は感じとは異なります。
10進法に関しては、桁数が固定され、すべての位置が9の場合、値は最大に達します。たとえば、最大4桁は9999です。
バイナリについても同様です。正の数を表す最上位ビット0を除いて、残りの位置がすべて1の場合、値は最大に達します。たとえば、最大8桁は01111111で、対応する10進数は127です。
バイトの長さは8ビットであるため、バイトが表すことができる最大の正の数は127、つまり、正の境界値である71を含む0です。
負の数を観察することにより、負の数を意味する最上位ビット1を除いて、次の7桁がすべて0の場合、負の数の最小値、つまり7 0の1であり、対応する10進数は負の境界である-128になります。価値がある。
そして、正と負の境界値は関連しています、あなたは見つけましたか?つまり、正の境界値に1を加算した後の反対の数は、負の境界値です。
バイナリの通常の操作
これらの内容は非常によく知られているはずです。
ビット操作
そして:
1&1-> 1
0&1-> 0
1&0-> 0
0&0-> 0
または(または):
0 | 0-> 0
0 | 1-> 1
1 | 0-> 1
1 | 1-> 1
ない(ない):
〜0-
> 1〜1-> 0
排他的OR(xor):
0 ^ 1-> 1
1 ^ 0-> 1
0 ^ 0-> 0
1 ^ 1-> 0
シフト操作
左に移動(<<):
左側は破棄され(符号ビットは引き続き破棄されます)、右側は0で埋められます。
シフト後の最上位ビットは、正の数の場合は0、負の数の場合は1です。
1ビットを左にシフトすることは2を掛けることに相当し、2ビットは4を掛けることに相当します。
1サイクル左に移動すると、原点に戻ります。それは動かないのと同じです。
複数の期間が経過した後、期間の部分を削除し、残りを移動します。
移動するビット数がバイナリ自体の長さと等しい場合、それはピリオドと呼ばれます。8ビット長のバイナリシフト8ビットなど。
右に移動(>>):
右側は破棄され、正の数値は左側に0で埋められ、負の数値は左側に1で埋められます。
1ビットを右にシフトすることは2で割ることに相当し、2桁は4で割ることに相当します。
丸めるときは、正の数の場合は丸め、負の数の場合は丸めを選択します。
正の数値を右にシフトすると、すべてを破棄することから始まり、左から追加された値はすべて0であるため、サイクルに達するまで、元の値に戻ります。つまり、元の値に戻ります。動かないのと同じです。
負の数値を右にシフトすると、破棄された時点から-1になります。これは、左から追加された数値がすべて1であるため、サイクルに達するまで、原点に戻る、つまり元の値に戻るためです。動かないのと同じです。
複数の期間が経過した後、期間の部分を削除し、残りを移動します。
符号なし右シフト(>>>):
右側は破棄され、正または負の数値の左側は0で埋められます。
したがって、正の数の場合、右シフト(>>)の間に違いはありません。
負の数の場合は正の数になります。つまり、元の補数形式を使用し、右側を破棄して正の数として扱います。
署名されていないシフトが残っていないのはなぜですか?
なぜなら、左にシフトすると、右に0が追加され、符号ビットが左端にあり、右に追加されたものはそれに影響を与えないからです。
サイクルに達した後、もう一度移動すると影響を受けると考える人もいるかもしれませんが、ハハ、サイクル中にゼロにリセットされます。
バイナリ展開/収縮
以下の内容は、下位バイトの前の上位バイトの順序を想定しています。
ストレッチ:
1バイトを2バイトに拡張する場合は、上位バイトを埋める必要があります。(バイトタイプをショートタイプに割り当てるのと同じです)
実際、このバイトは変更されず、左側に別のバイトが追加されます。
このとき、符号と値はどちらも変更されません。
正の数の符号ビットは0であり、上位バイトは引き伸ばされると0で埋められます。
00000110-> 00000000,00000110
負の数の符号ビットは1であり、上位バイトは引き伸ばされると1で埋められます。
11111010-> 11111111,11111010
シュリンク:
2バイトを1バイトに圧縮するには、上位バイトを切り捨てる必要があります。(ショートタイプをバイトタイプに強制するのと同じです)
実際、左側のバイトは直接破棄され、右側のバイトは変更されません。
このとき、符号と値の両方が変わる可能性があります。
圧縮されたバイトがまだこの数に収まる場合、符号と値のサイズは変更されません。
具体的には、正の数の上位バイトがすべて0の場合、下位バイトの最上位ビットも0になります。または負の数の上位バイトはすべて1であり、下位バイトの上位バイトも1です。上位バイトを切り捨てても、数には影響しません。
00000000,00001100-> 00001100
11111111,11110011-> 11110011
圧縮されたバイトがこの数に収まらない場合は、値を変更する必要があります。
具体的には、正の数値の上位バイトがすべて0でなく、負の数値の上位バイトがすべて1でない場合、上位バイトを切り捨てると、数値のサイズに確実に影響します。
符号が変わるかどうかは、元の符号ビットが圧縮された符号ビットと同じであるかどうかによって異なります。
たとえば、圧縮後にサイズが変更され、シンボルは次のように変更されません。
00001000、00000011を00000011に圧縮、まだ正11011111、11111101を
11111101に圧縮、まだ負
たとえば、サイズとシンボルは、圧縮後に次のように変更されます。
00001000,10000011は10000011に圧縮され、正の数は負の数になります。
11011111、01111101は01111101に圧縮され、負の数は正の数になります。
整数のシリアル化と逆シリアル化
一般的に、int型は4バイトで構成されます。シリアル化する場合、これらの4バイトを1つずつ分離し、順番にバイト配列に入れる必要があります。
デシリアライズするときは、バイト配列からこれらの4バイトを取り出し、順番に接続して、int型番号として再解釈します。結果は変更されないはずです。
シリアル化では、シフトと圧縮が主に使用されます。
最初に分割するバイトを最下位ビット(つまり、右端)に移動してから、バイトタイプへの変換を強制します。
次のようなintタイプ番号がある場合:
11111001,11001100,10100000,10111001
最初のステップは、24ビットを右にシフトし、下位8ビットのみを保持することです。
バイトb3 =(バイト)(i >> 24);
11111111,11111111,11111111,11111001
11111001
2番目のステップは、16ビットを右にシフトし、下位8ビットのみを保持することです。
バイトb2 =(バイト)(i >> 16);
11111111,11111111,11111001,11001100
11001100
3番目のステップは、8ビットを右にシフトし、最下位の8ビットのみを保持することです。
バイトb1 =(バイト)(i >> 8);
11111111,11111001,11001100,10100000
10100000
3番目のステップは、0ビットを右にシフトし、下位8ビットのみを保持することです。
バイトb0 =(バイト)(i >> 0);
11111001,11001100,10100000,10111001
10111001
このようにして、4つのバイトが生成され、それらをバイト配列に入れるだけです。
byte [] bytes = new byte [] {b3、b2、b1、b0};
デシリアライズする場合、主な用途は伸びと変位です。
最初にバイト配列からバイトを取得し、それをintタイプに変換してから、シンボルの問題に対処してから、左に移動して適切な位置に移動します。
最初の一歩:
最初のバイトを取り出し、
11111001
次に、intにストレッチします。
11111111,11111111,11111111,11111001
その符号ビットは元の整数の符号ビットを表すため、符号を処理する必要はなく、24ビットだけ左に直接シフトされます。
11111001,00000000,00000000,00000000
2番目のステップ:
2番目のバイトを取り出し、
11001100
次に、intにストレッチします。
11111111,11111111,11111111,11001100
その符号ビットは元の整数の中央にあるため、符号ではなく値を表します。符号ビットを処理する必要があります。これは、AND演算を実行するためです。
次のように、上記の2行と3行目を取得します。
11111111,11111111,11111111,11001100
00000000,00000000,00000000,11111111
00000000,00000000,00000000,11001100
次に、16ビット左にシフトします
00000000,11001100,00000000,00000000
3番目のステップ、
3番目のバイトを取り出し、
10100000
次に、intにストレッチします。
11111111,11111111,11111111,10100000
次に、符号ビットを処理します。
00000000,00000000,00000000,10100000
次に、8ビット左にシフトします。
00000000,00000000,10100000,00000000
4番目のステップ、
4番目のバイトを取り出し、
10111001
次に、intにストレッチします。
11111111,11111111,11111111,10111001
次に、符号ビットを処理します。
00000000,00000000,00000000,10111001
次に、0ビット左にシフトします。
00000000,00000000,00000000,10111001
この4つのステップにより、次の4つの結果が得られました。
11111001,00000000,00000000,00000000
00000000,11001100,00000000,00000000
00000000,00000000,10100000,00000000
00000000,00000000,00000000,10111001
4バイトがすでにあるべき位置にあることがわかります。
最後に、追加操作で十分です。実際、OR操作も可能です。
i = i4 + i3 + i2 + i0
i = i4 | i3 | i2 | i0
このようにして、バイト配列の4つのバイトをint型の数値に合成します。
符号なしの数値をシミュレートする
符号なしの数値は、最上位ビットが符号ビットではなく値ビットであることを意味します。
Javaなどの一部の言語は符号なしの番号をサポートしていないため、実装をシミュレートするには符号付きの番号を使用する必要があります。
符号なし番号と同じタイプの範囲は符号付き番号の範囲よりも大きくなるため、短いタイプの符号なし番号を格納するために長いタイプが使用されます。
バイトタイプがバイトの場合、符号付き番号として使用する場合の範囲は-128〜127であり、符号なし番号として使用する場合の範囲は0〜255であるため、格納するには少なくとも2バイトの短いタイプが必要です。
処理方法は非常に単純で、符号ビットを拡張して処理する2つのステップだけです。
バイトが10101011の場合、これは負の数のバイトタイプです。
最初のステップであるstretchは2バイトになりますが、それでも負の数です
11111111,10101011
2番目のステップは、シンボルを処理することです。つまり、AND演算を実行します。
11111111,10101011
00000000,11111111
00000000,10101011
これは、1バイトの負の数から2バイトの正の数まで処理されました。
実際、すべて0のバイトを元のバイトの前(つまり、左側)に追加することです。
バイトが符号なしの数値として使用され、最大値の255に達すると、バイナリは次のようになります。
00000000,11111111
現時点では、ローポジションが使用されています。
したがって、long型を使用してshort型の符号なし番号を表すと、long型のバイトの最高の使用効率は50%になります。
この場合、シリアル化中にバイトの下半分を書き込むだけで済みます。
デシリアライズする場合、1つはlong型を使用して継承することであり、もう1つは、すべてのバイトを符号付きで処理し、符号なしの数値として処理する必要があることです。
PS:これは10年前の大学の専門コースの基本的な知識の真剣なレビューです。
実際、「Product Spring」シリーズの記事を書いているときに、Javaバイトコード(.class)ファイルの内部構造に精通していることが最善であることがわかりました。
バイトコードファイルを解析しようとすると、符号なしの番号が格納されていることがわかったので、バイト配列を符号なしの番号に逆シリアル化するツールを作成する必要があります。
ツールを作成するときは、JDKの関連部分のソースコードを少し読んで、コードを作成し、バイナリの基本的な知識と操作をテストしました。
だから私はこの記事をまとめました、ハハ。
記事のすべてのコード例:https:
//github.com/coding-new-talking/java-code-demo.git
(終わり)