2進数
心の準備をしてほしい。 数というごく単純なものについて、これまで当たり前だと思っていた認識が覆されることになる。 これまでの人生では、「100個」のものと言われたら、次のような量を思い浮かべていたはずである。
しかし実際には、次のような量を意味していたのかもしれない。
いや、心配しなくていい。 これは何かの錯覚や思考実験ではなく、単に基数という考え方の違いにすぎない。 あなたはたった今、2進数の世界に足を踏み入れ、基数の異なる数体系に出会ったのである。
数体系と基数
数体系とは、数を表現するための方法である。 私たちは小学校のころからずっと10進数という数体系の心地よい枠組みの中で過ごしてきたが、数体系には他にも数多くの種類がある。 2進数、8進数、16進数、20進数など、基数の異なる数体系は無数に存在するが、電気工学において特に重要なものはそのうちごく一部である。
とりわけよく使われる数体系には、それぞれ固有の名前が付いている。 たとえば10進数はdecimal(10進法)と呼ばれる。 今回取り上げる2進数はbinary(2進法)と呼ばれ、もうひとつよく使われる16進数はhexadecimal(16進法)と呼ばれる。
数の基数は、値の末尾に小さく添えた整数(下付き文字)で表されることが多い。 先ほどの例で言えば、最初の画像は実際には「100₁₀」個であり、2番目の画像は「100₂」個ということになる。 基数を明示することで、値の解釈があいまいになるのを防げる。
なぜ2進数を使うのか
なぜ2進数なのか、と疑問に思うかもしれないが、それを言うならなぜ10進数なのだろうか。 私たちはずっと10進数を使い続けてきたため、日常生活で10進数を採用した理由についてはあまり深く考えたことがないはずである。 指が10本あるからかもしれないし、あるいは古代ローマ人がその属州にこの数え方を強制したからかもしれない。 理由が何であれ、これまでに身につけてきたさまざまな工夫によって、10進数は私たちの生活にしっかりと根付いている。 誰もが10ずつ数えることができるし、大きな数を10の倍数に丸めることさえする。 私たちは10という数にすっかり夢中なのである。
一方、コンピュータや電子機器には指も足の指もない。 もっとも基本的なレベルでは、これらは何かの状態をたった2通りの方法でしか表現できない。 オンかオフか、ハイかローか、1か0かである。 そのため、ほぼすべての電子機器は、数を保存し、操作し、計算するために2進数、つまり基数2の数体系に依存している。
電子機器がこれほど2進数に依存しているということは、2進数の仕組みを理解しておくことが重要だということでもある。 コンピュータプログラムのいたるところで、2進数やその親戚である16進数に出会うことになる。 デジタルロジック回路や、その他の非常に低レベルな電子回路の解析にも、2進数の知識が大いに役立つ。
このチュートリアルでは、10進数に対して行えることは、2進数に対しても行えるということが分かるはずである。 中には2進数のほうが簡単な演算もあれば(他方で面倒になる演算もあるが)、そういったことも含めてこのチュートリアルで扱っていく。
このチュートリアルは、かなり抽象的な概念にまで踏み込んでいく。 内容の多くは、足し算、掛け算、割り算(余りを含む)、指数といった数学的な概念の上に成り立っている。
電子工作の予備知識は必要ない(ほとんどの人がすでに知っている10進数の仕組みを理解していれば十分である)が、このチュートリアルではArduinoのプログラミングにおける2進数の使われ方にも触れるため、データ型についての知識があると役立つ場面がある。 また、デジタルロジックの知識があると、2進数についての理解をさらに深めるのに役立つ。
数え方と変換
各数体系の基数はradix(基数)とも呼ばれる。 10進数の基数は10であり、2進数の基数は2である。 基数によって、その数体系を成り立たせるために必要な記号の種類の数が決まる。 10進数では、0個からちょうど10個未満までの値を表すために、0、1、2、3、4、5、6、7、8、9という10種類の数字表記を使う。 これらの記号はそれぞれ、非常に明確で標準化された値を表している。
2進数で使える記号はたった2種類、0と1だけである。 しかしこの2種類の記号だけを使って、10進数で表せるどんな数でも作り出すことができる。
2進数での数え方
10進数であれば、眠っていてもできるほど際限なく数えられるが、2進数ではどう数えればよいだろうか。 2進数における0と1は、見慣れた0と1そのままである。 そこから先が、いよいよ2進数らしくなってくる。
使える数字が2種類しかないため、10進数のときと同じように、使える記号を使い果たしたら1桁左にずれ、そこに1を加えて、それより右側の桁はすべて0に戻す必要がある。 つまり1の次は10になり、次に11、そして100になる。 実際に数えてみよう。
| 10進数 | 2進数 | 10進数 | 2進数 | |
|---|---|---|---|---|
| 0 | 0 | 16 | 10000 | |
| 1 | 1 | 17 | 10001 | |
| 2 | 10 | 18 | 10010 | |
| 3 | 11 | 19 | 10011 | |
| 4 | 100 | 20 | 10100 | |
| 5 | 101 | 21 | 10101 | |
| 6 | 110 | 22 | 10110 | |
| 7 | 111 | 23 | 10111 | |
| 8 | 1000 | 24 | 11000 | |
| 9 | 1001 | 25 | 11001 | |
| 10 | 1010 | 26 | 11010 | |
| 11 | 1011 | 27 | 11011 | |
| 12 | 1100 | 28 | 11100 | |
| 13 | 1101 | 29 | 11101 | |
| 14 | 1110 | 30 | 11110 | |
| 15 | 1111 | 31 | 11111 |
パターンが見えてきただろうか。 続いて、2進数を10進数に変換する方法を見ていこう。
2進数を10進数に変換する
2進数から10進数への変換方法はひとつではない。 ここでは2つの方法を紹介する。 ひとつは数式を使う「数学寄り」の方法、もうひとつはより視覚的な方法である。 両方紹介するが、1つ目の方法で使う用語が難しく感じたら、2つ目の方法まで読み飛ばしてもかまわない。
方法1
2進数を10進数に変換するのに使える便利な関数がある。
各桁の値に、その桁の重み(2の累乗)を掛けて、すべて足し合わせるという方法である。 この式には4つの重要な要素がある。
- 各桁の数字:これはおなじみの0と1のことだが、2進数では必ず0か1のどちらかでなければならない。
- 桁の位置:これも重要な要素である。位置は一番右の桁を0として始まり、この1または0が最下位桁になる。左に1つ移動するごとに、その桁の重要度(位取り)も1つずつ増していく。
- 2進数の長さ:これは変数nの値によって決まる(正確には「n+1」)。たとえば101という2進数の長さは3であり、10011110のようなより大きな数の長さは8である。
- 重み:各桁には、2ⁿ、2ⁿ⁻¹、…、2¹のような重みが掛けられる。一番右の重みである2⁰は1に等しく、1桁左に移動するごとに2、4、8、16、32、64、128、256……と続いていく。2の累乗は2進数においてきわめて重要であり、すぐに馴染み深いものになるはずである。
nや指数の記号を取り除いて、8桁分の2進数の位取り記数法の式を具体的に書き出してみよう。
値 = a7×2⁷ + a6×2⁶ + a5×2⁵ + a4×2⁴ + a3×2³ + a2×2² + a1×2¹ + a0×2⁰
さらに一歩進めて、各桁に具体的な数字を当てはめてみよう。 たとえば2進数10011011があったとすると、各桁の値は次のようになる。
念のため、このチュートリアルでは一番右の値を常に最下位桁とすることを前提とする。 最下位桁とは、数全体の値への影響がもっとも小さい桁のことである。 桁の重要度をどちらの端から数えるかは、実は取り決めの問題であり、これはエンディアンと呼ばれる規則の一部である。 2進数は、最上位桁が一番左にある「ビッグエンディアン」と、このチュートリアルで採用する「リトルエンディアン」のどちらの方式でも表せる(2進数はたいていリトルエンディアンで表記されているのをよく見かけるはずである)。
それでは、これらの桁の値を先ほどの2進数から10進数への変換式に当てはめてみよう。 この数はリトルエンディアンなので、最下位の値には一番小さい重みを掛けることになる。
1×2⁷ + 0×2⁶ + 0×2⁵ + 1×2⁴ + 1×2³ + 0×2² + 1×2¹ + 1×2⁰
これを整理すると、10進数の値が求められる。
= 1×128 + 0×64 + 0×32 + 1×16 + 1×8 + 0×4 + 1×2 + 1×1
= 128 + 16 + 8 + 2 + 1
= 155
10進数に比べて2進数で数を表すにははるかに多くの桁数が必要になることにすぐ気づくはずだが、それでもたった2種類の数字だけですべてを表せているのである。
方法2
2進数を10進数に変換するもうひとつの、より視覚的な方法は、各桁の1と0を「箱」に振り分けることから始まる。 それぞれの箱には、おなじみの1、2、4、8、16……という、2の累乗の重みが割り当てられている。 8桁分の箱を並べると、次のようになる。
| 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
|---|
先ほどの2進数10011011をこれらの箱に振り分けると、次のようになる。
| 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
|---|---|---|---|---|---|---|---|
| 1 | 0 | 0 | 1 | 1 | 0 | 1 | 1 |
値が0になっている箱は、線を引いて取り除いてしまおう。
| 128 | 16 | 8 | 2 | 1 |
|---|---|---|---|---|
| 1 | 1 | 1 | 1 | 1 |
残った重みをすべて足し合わせれば、求める数が得られる。
128 + 16 + 8 + 2 + 1 = 155
10進数から2進数への変換
2進数から10進数への変換と同じく、10進数から2進数への変換にも複数の方法がある。 最初の方法は割り算と余りを使うもので、2つ目は引き算を使うものである。 両方試してみて、使いやすいほうを選んでほしい。
方法1
10進数を2進数に変換するのは、その逆ほど単純ではない。 この変換では、10進数を0になるまで2で繰り返し割っていく。 割るたびに得られる余りが、作成する2進数の1桁になる。
余りの求め方を忘れてしまった場合は、2で割っているので、割られる数が偶数なら余りは0、奇数なら余りは1になることを思い出してほしい。
たとえば155を2進数に変換する場合は、次のような手順になる。
155 ÷ 2 = 77 余り 1(一番右の桁、1桁目)
77 ÷ 2 = 38 余り 1(2桁目)
38 ÷ 2 = 19 余り 0(3桁目)
19 ÷ 2 = 9 余り 1
9 ÷ 2 = 4 余り 1
4 ÷ 2 = 2 余り 0
2 ÷ 2 = 1 余り 0
1 ÷ 2 = 0 余り 1(8桁目)
最初に得られた余りが最下位(一番右)の桁になるため、上から下へと読んでいけば、右から左へと2進数を組み立てられる:10011011。 先ほどの例と照らし合わせてみてほしい。ぴったり一致するはずである。
方法2
割り算と余りを求める方法が性に合わない場合は、もっと簡単な方法もある。 まず、変換したい10進数より小さい範囲でもっとも大きい2の累乗を見つけ、それを10進数から引く。 そして、0になるまで、可能な限り大きい2の累乗を使ってこの引き算を繰り返す。 引き算に使われた重みの位置には2進数の1が、使われなかった位置には0が入る。
先ほどの例を続けると、155は128で引くことができ、27が残る。
155 - 128 = 27
| 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
|---|---|---|---|---|---|---|---|
| 1 |
新しく得られた27は、64でも32でも引くことができない。 この2つの位置には0が入る。 16では引くことができ、11が残る。
27 - 16 = 11
| 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
|---|---|---|---|---|---|---|---|
| 1 | 0 | 0 | 1 |
11は8で引くことができ、3が残る。 その後、4では引くことができない。
11 - 8 = 3
| 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
|---|---|---|---|---|---|---|---|
| 1 | 0 | 0 | 1 | 1 | 0 |
3は2で引くことができ、1が残る。 そして最後に、1は1で引くことができ、0になる。
3 - 2 = 1
1 - 1 = 0
| 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
|---|---|---|---|---|---|---|---|
| 1 | 0 | 0 | 1 | 1 | 0 | 1 | 1 |
これで2進数が求められた。
変換用の計算ツール
幸いなことに、2進数から10進数へ、あるいはその逆を変換してくれる計算ツールはインターネット上に数多く存在する。 手計算で変換する必要に迫られることは、実際にはそう多くない。
ビット、ニブル、バイト
2進数の構造について説明する中で、数の長さについて簡単に触れた。 2進数の長さとは、その数に含まれる1と0の個数のことである。
よく使われる2進数の長さ
2進数の値は、よく一定の桁数(1と0の個数)にまとめられ、この桁数を数の長さと呼ぶ。 2進数の長さとしてよく使われるのが、ビット、ニブル、バイトである。 2進数の中の1つの1または0をビットと呼ぶ。 そこから、4ビットのまとまりをニブル、8ビットのまとまりをバイトと呼ぶ。
バイトは、2進数を扱う際によく耳にする言葉である。 プロセッサはすべて、あらかじめ決められたビット数で動作するように作られており、その長さはたいていバイトの倍数、つまり8、16、32、64ビットなどになっている。
まとめると、次のようになる。
| 長さ | 名称 | 例 |
|---|---|---|
| 1 | ビット | 0 |
| 4 | ニブル | 1011 |
| 8 | バイト | 10110101 |
ワードという言葉も、長さを表す用語として時折使われる。 ワードの語感はニブルやバイトほど親しみやすくはなく、意味もややあいまいである。 ワードの長さは、たいていプロセッサのアーキテクチャによって決まる。 16ビットのこともあれば、32ビット、64ビット、あるいはそれ以上のこともある。
先頭のゼロによる桁揃え
2進数の値を1バイト分(あるいはそれ以上)の桁数で表そうとすると、数を8桁にするために先頭に0を追加する必要が出てくることがある。 先頭のゼロとは、数の中でもっとも上位にある1のビットより左側に付け加えられた、1つ以上の0のことである。 10進数では先頭にゼロを付けることはあまりない。007と書いても、7という値について何か新しい情報が加わるわけではない(むしろ別の意味に取られるかもしれない)。
2進数の値に先頭のゼロを付けることは必須ではないが、その数のビット長についての情報を伝えるのに役立つ。 たとえば、1という数を00000001と表記することで、これがバイト単位で扱われていることを示せる。 どちらの数も同じ値を表しているが、前に7つの0が付いた表記のほうが、その値のビット長についての情報を余分に持っていることになる。
ビット単位の演算子
2進数の値を操作する方法はいくつもある。 10進数と同じように、2進数に対しても標準的な数学演算、つまり足し算、引き算、掛け算、割り算を行うことができる。 また、ビット単位の演算子を使えば、2進数の値の個々のビットを操作することもできる。
ビット単位の演算子は、1つまたは2つの2進数全体に対して、ビットごとに処理を行う関数である。 これらはブール論理を使って2進数の記号の集合に対して働くもので、電子工学とプログラミングの両方で幅広く使われている。
補数(NOT)
2進数の値の補数を求めるとは、その値のあらゆる要素をちょうど正反対にすることに等しい。 補数関数は、ある数を見て、すべての1を0に、すべての0を1に変える。 補数演算子はNOTとも呼ばれる。
たとえば10110101の補数を求めると、次のようになる。
NOT 10110101(10進数で181)
-------- =
01001010(10進数で74)
NOTは、1つの2進数の値だけに作用する唯一のビット単位演算子である。
OR
ORは2つの数を受け取り、それらの和集合を作り出す。 2つの2進数をORで演算する手順は次のとおりである。 まず、それぞれの数のビットの位置をそろえ、同じ位置にあるビットどうしを比較する。 それぞれのビットの比較において、どちらか一方でも、あるいは両方が1であれば、その桁の結果は1になる。 両方の値がその位置で0であれば、結果もその位置で0になる。
ORで起こりうる4通りの組み合わせと、その結果は次のとおりである。
- 0 OR 0 = 0
- 0 OR 1 = 1
- 1 OR 0 = 1
- 1 OR 1 = 1
たとえば10011010と01000110のORを求めるには、それぞれの数をビットごとにそろえる。 どちらか一方、あるいは両方の数の同じ桁に1があれば、結果もその桁は1になる。
10011010
OR 01000110
-------- =
11011110
ORの演算は、桁上がりのない2進数の足し算だと考えるとよい。 0足す0は0だが、1に何を足しても結果は1になる。
AND
ANDは2つの数を受け取り、それらの論理積を作り出す。 ANDは、演算対象となる両方の値がともに1である場合にのみ、1を生み出す。
2つの2進数をANDで演算する手順は、ORの場合とよく似ている。 まず、それぞれの数のビットの位置をそろえ、同じ位置にあるビットどうしを比較する。 それぞれのビットの比較において、どちらか一方でも、あるいは両方が0であれば、その桁の結果は0になる。 両方の値がその位置で1であれば、結果もその位置で1になる。
ANDで起こりうる4通りの組み合わせと、その結果は次のとおりである。
- 0 AND 0 = 0
- 0 AND 1 = 0
- 1 AND 0 = 0
- 1 AND 1 = 1
たとえば10011010と01000110のANDを求めるには、それぞれの値をそろえる。 それぞれの桁の結果は、その桁の両方のビットがともに1である場合にのみ1になる。
10011010
AND 01000110
-------- =
00000010
ANDは、掛け算のようなものだと考えるとよい。 0を掛ければ、結果は必ず0になる。
XOR
XORは排他的論理和である。 XORは通常のORと似た動作をするが、どちらか一方だけが1である場合にのみ1を生み出すという点が異なる。
XORで起こりうる4通りの組み合わせと、その結果は次のとおりである。
- 0 XOR 0 = 0
- 0 XOR 1 = 1
- 1 XOR 0 = 1
- 1 XOR 1 = 0
たとえば10011010と01000110のXORを求めると、次のようになる。
10011010
XOR 01000110
-------- =
11011100
2桁目に注目してほしい。 2つの1どうしをXORした結果、0になっている。
ビットシフト
ビットシフトは、これまで紹介してきたようなビット単位の演算子とは厳密には異なるが、1つの2進数の値を操作するのに便利な手段である。
ビットシフトには2つの要素がある。シフトさせる方向と量である。 数は左右どちらの方向にもシフトでき、1ビットだけ、あるいは複数ビットまとめてシフトすることもできる。
右方向にシフトすると、数の最下位ビット(右端)が1つ以上切り捨てられ、そのまま失われる。 ビット長をそろえるために、先頭にゼロを追加することもできる。
たとえば10011010を右に2ビットシフトすると、次のようになる。
右に2ビットシフト 10011010(10進数で154)
-------- =
00100110(10進数で38)
左方向にシフトすると、すべてのビットが数の最上位側(左側)へと押し出される。 シフトのたびに、最下位ビットの位置にゼロが追加される。
たとえば10011010を左に1ビットシフトすると、次のようになる。
左に1ビットシフト 10011010(10進数で154)
-------- =
100110100(10進数で308)
この単純なビットシフトは、実は比較的複雑な数学的な操作を行っている。 左にnビットシフトすると、その数は2ⁿ倍される(先ほどの例で入力値が2倍になっていたことに気づいただろうか)。 一方、右にnビットシフトすると、その数は2ⁿで整数除算される。 右シフトによる除算は少し奇妙に感じられることがある。 シフトによる除算で生じる端数は切り捨てられるため、154を右に2回シフトすると、154÷4=38.5ではなく38になる。 ビットシフトは、2、4、8などの数で割ったり掛けたりするための、非常に高速な手段になりうる。
これらのビット単位の演算子は、2進数に対して標準的な数学演算を行うために必要な道具のほとんどを提供してくれる。
プログラミングにおける2進数
もっとも基本的なレベルでは、2進数があらゆる電子機器を動かしている。 そのため、コンピュータプログラミングの中で2進数に出会うのは避けられないことである。
プログラム内での2進数の表現
Arduinoをはじめとするたいていのプログラミング言語では、2進数の前に0bを付けることで2進数として表現できる。
この0bが付いていなければ、その数は単なる10進数として扱われる。
たとえば、コード上で次の2つの数はまったく異なる値になる。
a = 0b01101010; // 10進数で106
c = 01101010; // 10進数で1,101,010(0bの接頭辞がないため10進数として扱われる)
プログラミングにおけるビット単位の演算子
前のセクションで紹介したビット単位の演算子は、いずれもプログラミング言語の中で実行できる。
ANDビット演算子
2つの2進数の値をANDで演算するには、アンパサンド(&)演算子を使う。
たとえば次のようになる。
x = 0b10011010 & 0b01000110;
// xは0b00000010になる
2進数の値をANDで演算することは、値にビットマスクを適用したり、ある2進数の特定のビットが1かどうかを調べたりするのに役立つ。
このANDビット演算子は、二重アンパサンド(&&)を使い、複数の論理式の入力に基づいて真偽値を生み出すAND条件演算子と混同しないように注意する必要がある。
ORビット演算子
ORビット演算子はパイプ(|、Shift+バックスラッシュのキーで、キーボードのBackspaceの下にあることが多い)である。
たとえば次のようになる。
y = 0b10011010 | 0b01000110;
// yは0b11011110になる
2進数の値をORで演算することは、数の中の1つ以上のビットを1に設定したいときに役立つ。
ANDのときと同じく、ORビット演算子を二重パイプ(||)のOR条件演算子と混同しないよう注意しよう。
NOTビット演算子
NOTビット演算子はチルダ(~、Shift+バッククォートのキーで、Tabキーの上にあることが多い)である。
たとえば次のようになる。
z = ~(0b10100110);
// zは0b01011001になる
XORビット演算子
2つの値をXORで演算するには、両者の間にキャレット(^)を置く。
r = 0b10011010 ^ 0b01000110;
// rは0b11011100になる
XORは、ビットが異なっているかどうかを調べるのに便利である。 なぜなら、片方が0でもう片方が1の場合にのみ1という結果になるからである。
左シフトと右シフト
2進数をnビット左または右にシフトするには、<<nまたは>>n演算子を使う。
いくつか例を見てみよう。
i = 0b10100101 << 4; // iを左に4ビットシフト
// iは0b101001010000になる
j = 0b10010010 >> 2; // jを右に2ビットシフト
// jは0b00100100になる
シフトは、2の累乗で掛け算や割り算をするための、特に効率のよい方法である。 先ほどの例では、左に4ビットシフトすることでその値は2⁴(16)倍になっている。 2つ目の例では、右に2ビットシフトすることでその数は2²(4)で割られている。
まとめ
2進数は、電子工学におけるあらゆる計算、演算、処理の土台となるものである。 ここからさらに学べることは数多くある。
2進数や、その他の重要な数体系についてさらに学びたいなら、次のようなキットやチュートリアルもおすすめである。
- バイナリブラスターの組み立てガイド:2進数の変換を練習したいなら、Binary Blaster Kitが役立つ。これははんだ付けが必要なキットである。組み立てたら、それを使って2進数、16進数、10進数の関係についての理解度を試すことができる。
- 16進数:この16進の数体系と、2進数や10進数との関係について学べる。
10進数と2進数の変換ができるようになったら、その知識を、文字が世界共通の方式でどのように符号化されているかを理解することにも応用できる。
また、身につけた知識を、低レベルな回路やICの理解にも応用できる。
さらに、通信プロトコルの中で2進数がどのような役割を果たしているかも見てみるとよい。
タグ: 通信、コンピュータ工学、プログラミング
出典:Binary(SparkFun Learn)を日本語に翻訳し、再構成した。 原文は CC BY-SA 4.0 ライセンスで公開されており、本ページも同ライセンスの下で提供する。