コンピュータと仲良くなるためには2進数の理解が欠かせません。
なぜなら、コンピュータのハードウェアは電子回路で構成されており、その基本的な動作は「オン」または「オフ」です。これは2進数の1と0に直接対応しています。したがって、ハードウェアの動作原理を理解するには、2進数が必須なのです。
また、プログラミングでも2進数は必要です。コンピュータ内での全てのデータ(数値、文字、画像、音声など)はバイナリ形式(2進数)で保存されます。例えば、Javaでint型は-2,147,483,648~2,147,483,647の範囲の整数を表現できるのですが、これは-231~231-1に由来します。さらに、 ビット演算のような低レベルのプログラミングにおいて、2進数の理解は非常に有用です。ビットマスク、シフト操作など、効率的なコードを書くためには2進数の理解が必要です。
さらに、データ通信も基本的にはバイナリデータの送受信で行われます。エンコードやデコード、エラー検出と修正など、通信の各層での処理を理解するためには、2進数の知識が必要です。例えば、インターネットの世界で住所に当たるIPアドレスは2進数の32桁(例.11001010.11111110.11101111.01011001)で表されます。
2進数ということはデジタルデータということです。
アナログデータは連続的な現象をそのまま表現し、デジタルデータはその現象を数値に変換して表現します。どちらもそれぞれの用途や状況に応じて適した形態を持ちますが、デジタルデータはその正確さとコンピュータでの処理の容易さから、現代の多くの技術において主流となっています。
ここでは2進数の基礎を学んでいきます。
1. 2進数の知識
1.1 コンピュータの本質は電気回路
コンピュータの本質は電気回路です。
電気回路には2つの状態があります。
電気が流れている状態と流れていない状態です。
電気回路に豆電球を一つ繋いで世界最小のコンピュータを作成してみましょう。
するとこのコンピュータでは2つの状態を表現できますね。
例えば、0と1という数が表現できますね。
他に何を表現することが可能でしょうか?
あなたの答え: |
もしも豆電球が2つに増えたら何種類の表現が可能でしょうか?
あなたの答え: |
豆電球の数を1から8まで増やして表現の数がどのように変化するかを以下にまとめてみましょう。
豆電球の数 | 表現の数 | 指数で表すと |
1 | ||
2 | ||
3 | ||
4 | ||
5 | ||
6 | ||
7 | ||
8 |
豆電球の数と表現範囲を一般化すると豆電球の数をnとしたらどのように書き表されますか?
あなたの答え: |
指数(や対数)を忘れてしまった人はリンクの記事を参照してください。
1.2 ビットとは
この2進数の1けたをビット(bit)といいます。英語で【a little bit】「ほんの少し」という表現を覚えている人も多いのではないでしょうか?
情報量の最小単位は2進数の1桁を表し、bitといいます。(binary digit=2進数)
2進数(バイナリ)は、基数(または底)が2である数値表現方法です。
2進数では、0と1の2つの数字(ビット)のみが使用されます。
コンピューターシステムでは、2進数が広く使用されています。これは、デジタル回路がオンとオフの2つの状態(通常は電圧の高い状態と低い状態に対応)を持つためです。
2進数は、このようなデジタル状態を自然に表現できるため、コンピューターシステムに適しています。
4ビットで考えて0から16までの10進数に対応する2進数を書いてみてください。(16は表現可能でしょうか?)
10進数 | 2進数(1回目) | メモ | 2進数(2回目) |
---|---|---|---|
0 | |||
1 | |||
2 | |||
3 | |||
4 | |||
5 | |||
6 | |||
7 | |||
8 | |||
9 | |||
10 | |||
11 | |||
12 | |||
13 | |||
14 | |||
15 | |||
16 |
2進数の各桁は、右から左に向かってそれぞれ2のべき乗を表します。
例えば、右から2番目の桁は2^1(2)を表し、右から3番目の桁は2^2(4)を表します。
したがって、2進数の数値を10進数に変換するには、各桁の値に対応する2のべき乗を掛けて合計を求めます。
例: 2進数の1010を10進数に変換する場合、
1 * 2^3 + 0 * 2^2 + 1 * 2^1 + 0 * 2^0 = 8 + 0 + 2 + 0 = 10
となります。したがって、2進数の1010は10進数で10です。
なお、n^0 (Nの0乗)は1です。2^0も10^0も16^0も-1^0も全て1ですので忘れてしまった人はここで覚えてください。
1.3 バイトとは(アスキーコード)
また、8ビットで1バイトという単位になります。(【bite】「かじる」という単語にちなんで、スペルを変更して"byte"とした。情報の一齧りという意味)
なぜ、8ビットが1バイトになったかというと256通りの表現ができれば英語圏ではすべての文字種を表現できるからです。(実際には7ビットで足る)
以下のアスキーコードを見てください。
10進数 文字 | 10進数 文字 | 10進数 文字 | 10進数 文字 |
---|---|---|---|
0 NUL | 32 Space | 64 @ | 96 ` |
1 SOH | 33 ! | 65 A | 97 a |
2 STX | 34 " | 66 B | 98 b |
3 ETX | 35 # | 67 C | 99 c |
4 EOT | 36 $ | 68 D | 100 d |
5 ENQ | 37 % | 69 E | 101 e |
6 ACK | 38 & | 70 F | 102 f |
7 BEL | 39 ' | 71 G | 103 g |
8 BS | 40 ( | 72 H | 104 h |
9 TAB | 41 ) | 73 I | 105 i |
10 LF | 42 * | 74 J | 106 j |
11 VT | 43 + | 75 K | 107 k |
12 FF | 44 , | 76 L | 108 l |
13 CR | 45 - | 77 M | 109 m |
14 SO | 46 . | 78 N | 110 n |
15 SI | 47 / | 79 O | 111 o |
16 DLE | 48 0 | 80 P | 112 p |
17 DC1 | 49 1 | 81 Q | 113 q |
18 DC2 | 50 2 | 82 R | 114 r |
19 DC3 | 51 3 | 83 S | 115 s |
20 DC4 | 52 4 | 84 T | 116 t |
21 NAK | 53 5 | 85 U | 117 u |
22 SYN | 54 6 | 86 V | 118 v |
23 ETB | 55 7 | 87 W | 119 w |
24 CAN | 56 8 | 88 X | 120 x |
25 EM | 57 9 | 89 Y | 121 y |
26 SUB | 58 : | 90 Z | 122 z |
27 ESC | 59 ; |
このようにすべての文字種が表現できています。(日本語は無理ですが)
1.4 基数変換
2進数は桁数が増えるので人間には分かりにくい表現です。
そこで一般的には16進数が使われます。
なぜなら2進数の4桁は16進数の1桁に収まるため見やすくなるからです。
また、10進数と2進数の相互変換もできないといけません。
そこで必要になるのが基数変換という考え方です。
基数変換とは、ある数値を一つの基数から別の基数に変換するプロセスです。
一般的な基数には、2進数(バイナリ)、8進数(オクタル)、10進数(デシマル)、16進数(ヘキサデシマル)があります。
数値表現に使用される基数は、その基数で使用可能な数字の数に等しくなります。
例えば、10進数では0~9までの10個の数字がありますし、16進数では0~9の数字に加えてA~Fの6つの英字が使われ、合計16個の記号が使われます。
以下に10進数、2進数、16進数を4ビットで考えてまとめてみましょう。
10進数 | 2進数(4ビット) | 16進数 |
---|---|---|
0 | ||
1 | ||
2 | ||
3 | ||
4 | ||
5 | ||
6 | ||
7 | ||
8 | ||
9 | ||
10 | ||
11 | ||
12 | ||
13 | ||
14 | ||
15 | ||
16 |
10進数と2進数の相互変換
10進数を2進数に基数変換するには、繰り返し除算を使います。
10進数の数値を2進数の基数である2で除算し、余りを記録します。
商が0になるまでこれを繰り返し、余りを逆順に並べることで目的の基数に変換された数値が得られます。
以下のスペースに講師の説明を写しましょう。
13を2進数に | 36を2進数に |
2進数と16進数の相互変換
2進数の4桁は16進数の1桁に当たります。
したがって10進数は2進数にしてから16進数にする。また、その逆もあり、ということで考えてください。(2進数を介在させない方法もありますので興味のある方は調べてください)
10進数16を2進数にしてから16進数にする | 10進数139を2進数にしてから16進数にする |
16進数は例えばCSSの色コードに使われています。色コードが学べるゲームへのリンクです。
1.5 負数の表現
これまではマイナスの数を考えませんでしたが、実用のコンピュータを考えた場合マイナスの数は必須です。
0と1しか理解できないコンピュータでマイナスの数を表現するにはどうしたらいいでしょうか?
例えば、
最上位ビット(最も左のビット)を符号ビットとして、正の数では0、負の数では1にする
とすればどうでしょうか?
同じく4ビットで考えてみましょう。
2の補数を求める方法は以下の2ステップです:
- まず、元の数値のビットを反転(0を1に、1を0に)します。(1の補数といいます)
- 反転させたビットに1を加算します。(2の補数といいます)
例: 10進数の -5 を4ビットの2進数の2の補数表現に変換する場合
10進数の 5 を2進数に変換: 0101
- ビットを反転: 1010
- 反転したビットに1を加算: 1010 + 1 = 1011
したがって、4ビットの2の補数表現で -5 は 1011 になります。
2の補数表現での負数の10進数への変換は次のように行います:
2の補数を求めて正の数値に変換します。
変換された正の数値を10進数に変換し、符号を負にします。
例: 4ビットの2進数の2の補数表現である 1011 を10進数に変換する場合
- 2の補数を求める: 1011 -> 0101
- 10進数に変換して符号を負にする: 0101 (2進数) = 5 (10進数) -> -5
このようにして、2の補数表現を使用して2進数で負数を表現および変換することができます。
10進数 | 4ビットの2進数(マイナスを考えない) | 4ビットの2進数(マイナスを考える) |
-8 | ||
-7 | ||
-6 | ||
-5 | ||
-4 | ||
-3 | ||
-2 | ||
-1 | ||
0 | ||
1 | ||
2 | ||
3 | ||
4 | ||
5 | ||
6 | ||
7 | ||
8 | ||
9 | ||
10 | ||
11 | ||
12 | ||
13 | ||
14 | ||
15 | ||
16 |
2進数で負数を表現するために2の補数表現を使用します。
2の補数表現では、最上位ビット(最も左のビット)が符号ビットとして機能し、正の数では0、負の数では1になるのでしたね。
2の補数表現は、負の整数をその絶対値の補数として表現し、正の整数と同じように加算や減算を行うことができます。
以下のスペースに講師の説明を書き入れましょう。
+1と-1を足して0になることの説明。 |
コンピュータは足し算の回路を使って引き算を実現できるにように作られているのです。もしも、足し算と引き算の回路を別々に作らないといけないとすれば、コストが高くなってしまっていたでしょうね。
2の補数を求める続けることで符号が+からー、ーから+に変化することの説明。 |
2の補数表現で表すとき桁数をnとして一般化するとどのような表現になりますか?
あなたの答え: |
このあと皆さんが学ぶJavaではint型という符号付整数を使います。
int型は32ビットなので-2,147,483,648~2,147,483,647の間の数値を表現できます。
1.6 実数の表現
これまでの説明でコンピュータによってプラスの値、マイナスの値を表現する方法がわかりました。
正の整数と負の整数は扱えるようになりましたね。
しかし、実数、すなわち小数を含む数も扱えないとコンピュータは使い物になりません。
コンピュータで実数を表現するにはどうしたらいいでしょうか?
例えば、4ビットで考えた場合に真ん中に小数点があると仮定してはどうでしょうか?
xx.xx
のように考えるのです。
この場合、マイナスを考慮しなければ00.00から11.11までの表現が可能になります。
10進数では0~3.75です。
講師が2進数の小数部分の考え方を板書しますので以下にメモして下さい。
x x x x . x x x x |
小数部分の2進数への基数変換
小数部分の数値を2進数に変換する方法は、以下の手順で行います。
- 小数部分を2倍します。
- その結果から整数部分を取り出します(0か1)。
- 整数部分を2進数の桁として記録します。
- 結果の小数部分を再び2倍し、ステップ2~3を繰り返します。
この手順を繰り返していくと、小数部分の2進数表現が得られます。ただし、有限桁の2進数で正確に表現できない小数の場合、手順を無限に繰り返すことになります。実際の計算では、必要な桁数に達したら、その時点で手順を終了します。
例として、0.625を2進数に変換してみましょう。
ノート: |
固定小数点数
固定小数点数【fixed-point number】は、コンピュータで実数を表現する方法の一つで、小数点の位置が固定された数値表現のためこの名前があります。
固定小数点数は、整数部と小数部に分かれており、各部分に割り当てられたビット数が固定されています。
固定小数点数表現のメリットは、この後説明する浮動小数点数に比べてハードウェアおよび計算がシンプルであることです。そのため、リソースが限られた環境やリアルタイムシステムでの利用に適しています。また、固定小数点数では誤差が一定であるため、誤差が予測可能で、浮動小数点数で発生しうる誤差に関連する問題を避けることができます。
一方で、固定小数点数表現には欠点もあります。浮動小数点数に比べて表現できる数値の範囲や精度が制限されることがあります。また、オーバーフローやアンダーフローに対処するための特別な処理が必要になることがあります。
固定小数点数を用いた計算は、通常の整数演算と同様に加算、減算、乗算、除算が行えますが、乗算や除算の結果を適切な範囲に収めるためには、シフト操作や丸め処理が必要になることがあります。
固定小数点数は、組み込みシステムやデジタル信号処理、リアルタイムシステムなど、計算速度やリソースの制約が厳しいアプリケーションでよく使用されます。現在の多くのプログラミング言語や開発環境では、固定小数点数をサポートしているため、浮動小数点数と同様に扱うことができます。例えば、みなさんがこのあと学ぶJavaではBigDecimal型が固定小数点数を扱うデータ型です。
浮動小数点数
浮動小数点数は限られたビットの中で非常に大きな数や小さな数を表現するテクニックです。
非常に大きな数や小さな数を扱う時、わたしたちは普段どのようにしているでしょうか?
2進数の前に10進数で考えてみましょう。
10進数の+1,200,000,000 | 10進数の-0.000,000,012 |
符号 仮数 × 基数 ^ 指数 | 符号 仮数 × 基数 ^ 指数 |
このように私たちは小数点の位置を変えることで大きな数や小さな数を少ない桁数で表現しています。これが浮動小数点数の基本となる考え方であり、名前の由来です。
浮動小数点数【floating-point number】は、コンピュータにおいて実数を近似的に表現するための方法です。
浮動小数点数は、非常に大きな数値と非常に小さな数値を同時に表現することができるため、科学計算やグラフィックスなど幅広いアプリケーションで利用されています。
浮動小数点数は、次の3つの部分からなります:
- 符号:数値が正(+)か負(-)かを表します。
- 仮数:数値の有効桁数を表します。
- 指数:数値のスケールを表します。
浮動小数点数は、次の形式で表されます。
± 仮数 × 基数 ^ 指数
通常、基数は2です。
コンピュータでは、浮動小数点数の表現にIEEE 754という標準が広く使われています。この標準には、単精度(32ビット)と倍精度(64ビット)の2つの形式があります。
単精度(32ビット)の浮動小数点数:
符号:1ビット
仮数:23ビット
指数:8ビット
倍精度(64ビット)の浮動小数点数:
符号:1ビット
仮数:52ビット
指数:11ビット
このあと皆さんが学ぶJavaでは単精度浮動小数点数はfloat、倍精度浮動小数点数はdoubleと呼ばれます。
浮動小数点数表現は、指数部分によって大きな範囲の数値を表現することができますが、仮数部分のビット数が限られているため、有効数字の桁数に制限があります。
このため、浮動小数点数で計算を行う際には、誤差が発生する可能性があります。
そのことを説明するために、次に0.3を浮動小数点数で表現してみましょう。ただし、形式は「基本情報技術者平成18年春期 午前問4」と同じとします。
ノート:0.3を浮動小数点数で表現する |
2. ビット演算
ビット演算とは、情報量の最小単位であるビット単位の演算のことです。
ビット演算には以下のようなメリットがあります。
1.高速な処理
ビット演算は、コンピュータにとって最も基本的な演算であり、CPUによって非常に高速に処理されます。そのため、ビット演算は、数値演算よりも高速な処理ができます。
2.メモリ使用量の削減
ビット演算は、1つのビット単位で操作を行うため、メモリ使用量が非常に少なくて済みます。これは、大規模なデータ処理を行う場合に非常に有用です。
ビット演算の理解にはブール代数の理解が必要ですのでまずはブール代数を説明します。
2.1 ブール代数
ブール代数は、真偽値を変数として扱い、論理演算子によって真偽値を組み合わせて論理式を作り出します。
2進数は、0と1の2つの数字を使って数値を表現する方法であり、真偽値を表すのにも適しています。
例えば、2進数での数値1は真を表し、0は偽を表します。
2進数変数Aの否定(NOT)を計算すると、以下のようになります。
A | NOT A |
0 | |
1 |
2つの2進数変数AとBの論理積(AND)と否定論理積(NAND)を計算すると、以下のようになります。
A | B | A AND B | A NAND B |
0 | 0 | ||
0 | 1 | ||
1 | 0 | ||
1 | 1 |
同様に、2つの2進数変数AとBの論理和(OR)、排他的論理和(XOR)、否定論理和(NOR)を計算すると、以下のようになります。
A | B | A OR B | A XOR B | A NOR B |
0 | 0 | |||
0 | 1 | |||
1 | 0 | |||
1 | 1 |
それぞれを講師がベン図にまとめますので下にメモしておきましょう。
NOT A | A AND B | A NAND B |
A OR B | A XOR B | A NOR B |
2.2 ビット演算
ビット演算は、ビットレベルでの演算子を使用して行われます。以下にビット演算子とその動作を説明します。
演算子 | 意味 | 使用例 | 結果例 | 実行結果を以下に2進数で書き入れることで確認してみましょう(下位4ビットを抜粋) |
& | ビット単位の論理積(AND) | 5 & 3 | 1 | 0101 0011 |
| | ビット単位の論理和(OR) | 5 | 3 | 7 | 0101 0011 |
^ | ビット単位の排他的論理和(XOR) | 5 ^ 3 | 6 | 0101 0011 |
~ | ビット単位の補数(NOT) | ~5 | -6 | 0101 |
<< | 論理左シフト | 5 << 1 | 10 | 0101 |
>> | 論理右シフト | 5 >> 1 | 2 | 0101 |
Javaを使って確認してみてください。
public class BitOperation {
public static void main(String[] args) {
System.out.println(5 & 3);
System.out.println(5 | 3);
System.out.println(5 ^ 3);
System.out.println(~5);
System.out.println(5 << 1);
System.out.println(5 >> 1);
}
}
2.3 ビット演算の使用事例
ビット演算の使用事例には様々なものがありますが、ここでは代表的なものを3つ挙げます。
掛け算と割り算
ビット演算を使い、掛け算と割り算をすることができます。
以下はそのサンプルです。
public class BitwiseArithmetic {
public static void main(String[] args) {
int a = 6; // 二進数で表すと 110
int resultMultiplication = a << 1; // 左シフト演算で乗算を行う
System.out.println("乗算結果: " + resultMultiplication); // 12が出力される
int resultDivision = a >> 1; // 右シフト演算で除算を行う
System.out.println("除算結果: " + resultDivision); // 3が出力される
}
}
乗算や除算にはビットのシフト演算を使います。
ただし、シフト演算だけでは2倍、4倍、8倍…あるいは、1/2倍、1/4倍、1/8倍…といった2進数で切りの良い演算しかできません。
それ以外の場合はどうしたら良いでしょうか?
OR演算を組み合わせます。
例えば、左シフト演算とOR演算で3倍を行う例は以下になります。
public class BitwiseTriple {
public static void main(String[] args) {
int x = 5; // 2進数で表すと 101
int result = (x << 1) | x; // 左シフト演算とOR演算で3倍を行う
System.out.println("3倍の結果: " + result); // 15が出力される
}
}
次に6倍にする例です。
2倍にしたものと4倍にしたものをOR演算で合成することで6倍の値を得ることができます。なお、OR演算の代わりに加算(+演算)でも同じようにビットを合成することができます。
public class BitwiseSextuple {
public static void main(String[] args) {
int x = 5; // 2進数で表すと 101
int result1 = (x << 1) | (x << 2); // 左シフト演算とOR演算で6倍を行う
System.out.println("6倍の結果: " + result1); // 30が出力される
int result2 = (x << 1) + (x << 2); // OR演算の代わりに+演算をしても同じこと
System.out.println("6倍の結果: " + result2); // 30が出力される
}
}
暗号処理
暗号処理は平文を暗号化することと暗号文を復号して平文に直す処理からなっています。
ビット単位の排他的論理和を使えば簡易的にこれらの処理を実現できます。つまり、文字コードのビットを反転することが暗号化に当たり、また、暗号文の文字コードのビットを更に反転することが復号に当たります。(この暗号は交換車の名前を取ってバーナム暗号と呼ばれています。かなり初期の暗号で単純ですが、鍵を知ることと元の平分を知ることは同じですから、とても強力な暗号です。ただし、毎回平分と同じだけの長さの鍵を用意しなければならないことが欠点です)
以下の例はその説明をしているサンプルコードです。(コードの詳細を理解する必要はありません)
import java.util.Arrays;
public class BitOperation2 {
public static void main(String[] args) {
char[] plainText = { 'A', 'B', 'C' };
char[] cipherText = new char[plainText.length];
final char key = 0xFFFF; // 暗号化キーは2進数1111111111111111(=65535)
System.out.println("暗号化前の平文:" + Arrays.toString(plainText));
// XOR操作を使用して平文を暗号化
for (int i = 0; i < plainText.length; i++) {
cipherText[i] = (char) (plainText[i] ^ key);
}
System.out.println("暗号化後の暗号文:" + Arrays.toString(cipherText));
// 暗号文に再度XOR操作を適用して元の平文を復元
for (int i = 0; i < cipherText.length; i++) {
cipherText[i] = (char) (cipherText[i] ^ key);
}
System.out.println("復号後の平文:" + Arrays.toString(cipherText));
}
}
<実行結果>
暗号化前の平文[A, B, C] 暗号化後の暗号文[ᄒ, ᄑ, ᄐ] 復号後の平文[A, B, C] |
マスク処理
※この内容はネットワークを学んだあとに復習に使ってください
マスク処理は任意のビットを0にするという処理です。
口にするマスクと同様に通したいビットはそのまま通して、通したくないビットは全て0にしてしまいます。
マスク処理にはビット単位の論理積を使います。
以下はIPアドレスにサブネットマスクを当てて下位8ビットを全て0にしてネットワークアドレス(クラスC相当)を取得する処理です。
なお、このプログラムは簡易的な説明のためのサンプルコードです。(コードの詳細を理解する必要はありません)
public class BitOperation1 {
public static void main(String[] args) {
int ipAddress = 0b11001010111111101110111101011001; // 0bは2進数であるということ IPアドレス 202.254.239.89
int subnetMask = 0b11111111111111111111111100000000; // 下位8ビットを取り出すためのマスク 255.255.255.0
System.out.println("IPアドレス:" + Integer.toBinaryString(ipAddress));
int networkAddress = ipAddress & subnetMask; // サブネットマスクを適用
System.out.println("ネットワークアドレス:" + Integer.toBinaryString(networkAddress));
int hostAddress = ipAddress & ~subnetMask; // サブネットマスクのNOTを適用
System.out.println("ホスト部:" + Integer.toBinaryString(hostAddress));
int broadcastAddress = ipAddress | ~subnetMask; // ネットワーク内のすべてのノードにデータを一斉配信するためのアドレス
System.out.println("ブロードキャストアドレス:" + Integer.toBinaryString(broadcastAddress));
}
}
<実行結果>
IPアドレス:11001010111111101110111101011001 ネットワークアドレス:11001010111111101110111100000000 ホスト部:1011001 ブロードキャストアドレス:11001010111111101110111111111111 |
サンプル問題解説 基本情報処理技術者試験 科目Bへのリンク
それでは、基本情報処理業者試験のサンプル問題の中からビット演算の問題にチャレンジしてみましょう。
最後までお読みいただきありがとうございました
次回は、「オペレーティングシステムを理解して作業効率を上げる」を学びます。