こんにちは。コンピュータ・アーキテクチャを意に介さぬ悪辣な文系プログラマー集合の1要素であるお前よ。また会ったね。お前は本当にどこの現場にもいるね。
今日は二進法の話を聞かせてあげよう。暖炉の前においで。寒かったろう。
さて、大好評秒読みの「テメーらは説明が下手なんだよゴミども」シリーズである。今日は二進数、二進法について。
ホントはコンピューターに繋いだディスプレイで文字が表示できる理由について話そうかと思ったんだけど、書き進めていくうちに予定が激しく狂ったんだ。だから二進法の話をするよ。
◆十進法
まずその、十進法について知っているだろうか。
あ、知らない。そう。…え?
…じゃあ、数字を思い浮かべろ。
0 1 2 3 4 5 6 7 8 9
何文字ある。数えろ。声を出して数えろ。
10文字あっただろ。(正しくない表現だが、)10個の文字を使ってありとあらゆる「数」を表現しようと試みるのが十進法だ。「基となる数」が10個あることを「基数が10」という。十進法の基数は10。
※負の数は「-」って文字使ってるし、少数だと「.」とか使ってるよね。それが「正しくない」ということ。「0を含む自然数」と絞って話せば正しいな。とりあえずの分かりやすさの為にこう説明しておく。Wikiをみろ。
繰り上がりという概念があるな。「9」に「1」を足すと、「9より1大きい数」を1文字で表現できなくなって繰り上がりが発生する。だから「10」になる。
この「0」、すなわち「ゼロ」という概念はアラビア数字に(略
そして、例えば「時刻」は60進法と24進法の複合技である。「1分60秒」は「2分00秒」であるから、60で桁がひとつ繰り上がっているわけだ。
十進法。十進法で表現される数値が十進数。
◆コンピューターが数値を扱うという事
二進法の話を始めてもいいんだけど、その前に二進法を知るべき理由について。
電気ってあんじゃん。その電気を使って数値を表現してみようと思う。なんで電気なのかって?そりゃ、コンピューターが火でも水でも砂でもなくて電気で動いているからだ。加えて、コンピューターにはなによりも計算をさせたいわけだから数値を表現できなければ始まらん。
まず「10万ボルトの電流(?)がながれてるので10万です!」とかいうお前の無邪気な発想はちょっとイケてない。ピカチュウ程度の知能しかないと言わざるを得ない。
なんでかって、電気は遠くまで流すと失われるから。超電導じゃない材に電気を流すと、遠くに行くほど弱まっていく。すなわち電気抵抗だ。だから途中途中で電気を強めてあげなければいけなくなる。あと、でっかい数字を表現するためにでっかい電力が必要になっちゃうでしょ?
そんなかんじ。
だから、電気の大きさ関係なく、電気のON/OFFで数字を表現しようという話になった。どうしよ。
そこでお前の為に、フリップフロップっていう魔法の機械を100台もってきました。電気のON/OFFをフリップフロップくんに覚えさせておくことができるのだ。やったね。
A太郎くんがB乃介くんに「ある数値」を伝言したいんだとして、じゃあ、フリップフロップくん100個でもって伝えてみよう。
A太郎くんは箱にフリップフロップくんを詰め込んだ。そして、眠ってるフリップフロップくんをつんつんして80個ONにし、B乃介君に着払いで郵送した。
B乃介くんは届いた箱を開封し、ONになっているフリップフロップくんを数えた。80個いた。じゃあ80だな!
というのもまぁ正しいんだけど、「100万」っていう数値を記憶するために100万のフリップフロップくんが必要になるじゃん。もうちょっと高効率に表現する方法はないものかね。
▼アイデア
じゃあ、フリップフロップくんのオデコに、「10個分」と書いておけばいいんじゃなかろうか。
さすれば、「10個分」と書かれているフリップフロップくん1個で「10」という数値を伝えることが出来る。すげぇ。
100個あるから、×10で1000まで表現できるようになったな。よっしゃああああ。
▼破綻
100個のフリップフロップくん全てに「10個分」と書いてしまった。
そうしたところ、なんと「1~9」が表現できなくなってしまった。「11」とかも。しまった。
▼改善
じゃあ、まぁ「1」を表現するために「1」と書かれたフリップフロップくんが1個必要だ。とはいえ全部「1」と書くと元のままなので、次のフリップフロップくんには「2」と書いてみる。
よしよし、良い感じだ。じゃあ次のフリップフロップくんには「3」と…
いや、待て。「1」と「2」のフリップフロップくんをONにすれば、足して3を表現できるじゃないか。じゃあ、「4」だな。フフ…フフフ…。
えぇと、5は、1 + 4 だからOKで…6…7…8…
8無理だな。1 + 2 + 4 が 7 だから、つぎ8だな!
~114514年後~
1 2 4 8 16 32 64 128 256 512 1024 2048 4096 8192
あ、これ、前の数を2倍した数の並びじゃねぇか。初項1で公比2の等比数列だぁぁぁ。うわぁぁあああ。
という感じで数値を記憶すれば効率的なんですね。ONとOFFだけで数字を表現せねばならん時は、このやりかたがよい。余談を挟んでから、もうちょっと詳しく説明する。
▽余談:アナログ回路
電気の流れとか圧の具合で値を表現するのはイケてないぜ。といったが、当然ながらその発想も実装されている。それがアナログ回路。逆に、ONとOFFをたくさん使って表現するのがデジタル回路。
オペアンプっていう(略
デジタルは digital と表記されるが、この「digit」というのは元々「指」という意味をもっていて、つまり、指でモノを数えるように離散的な(値と値の間がとぶ)もののとらえ方をする世界がデジタルだ。
アナログを指で表現しようとしたなら、「関節の曲がり具合」で数値を表現するようなものだな。0と1の間がスムーズに変化するだろう。それがアナログ。
アナログには「昔っぽい」という意味はないぜ。アナログテレビっていうのは(略
そんなもんで、例えば「デジタル体温計」という表記は、体温自体がデジタルで無いわけだからおかしいと言えばおかしい。センサーやらがA/D変換してるだけで(略
そういう意味で、「ラジオ放送」と「Bluetoothで音声を通信する」のはルールからして全く違うんですね。
◆二進法
左から1, 2, 4, 8…だとして、以下はどうなる
ON OFF OFF ON
一番ひだりの1と、一番みぎの8がONだから、9だな。
※フリップフロップくんがきゃいきゃいしている絵を描いたのだが、可愛かったので捨てた。
これが2進法の考え方。
現状に即すと、10進法みたいに右が最下位で表現されるし、基となる数は「0」「1」で表現される。つまり
0101
は、右から 1, 2, 4, 8だから、「5」を表現できているわけだ。
※オーダー(順番)、つまりビット列の扱い方は実装により違う。ここでは全部「左に行くほど大きいぜ」って感じで。
もいっこ。
0110 1010 1100 0101
これで 27,333 を表現できるわけだ。すごい。100個のフリップフロップしかなくて、2万とか表現無理だと思ったのに、すごい。15個で表現できてる。8個ONにするだけだから電気代も少なくて済む。すごい。
※空白は読みやすさのためにつけているだけ。
※Windows電卓には二進モードがあるんで、計算が楽。「プログラマー」の「BIN」を選択しろ。
◆二進法の加減算
かんたん。
1 + 1 = 10
うん。
10 + 10 = 100
うん。
繰り上がりするだけ。筆算すればすぐにわかる。引き算もそう。
◆乗除
筆算すれば簡単。
※電気的にはちょい難しい。1記事書けちゃう話だから割愛する。
◆マイナス
ゼロとイチでしか数字を表現できないんで、「負の値」すなわちマイナスをどう表現しようかっていう話になるだろう。
フリップフロップくんの話を応用すると、どれかひとつのオデコに「マイナス」と書いてやればいい。
でも実際の所、計算のコストやらを考えた時に別の表現方法が必要になるんですね。
▼加算器
さっき「二進法の加算は、筆算すれば余裕」という事を言ったが、コンピューターが何か計算するときって、最終的にはやっぱり電子回路を使って計算することになる。
例えば「0101」と「0011」の足し算をするときも、電気の流れで計算せねばならない。
ANDとORという(略
MOSFET(略
つまり、ALUっつー、CPUに載ってる機械の上にさ、加算器という、「足し算が大得意な回路」が既にあるんです。
減算というものは、「マイナスを加算する」という表現も可能だな。それをゼロとイチだけで表現したくなってきてワクワクしてきた。
つまり、「A ひく B」という計算を考えた時に、「B」の値をいじくって足し算だけで済ませたいのだ。そのためにまず「桁あふれ」というものについて理解していただく。
▼桁あふれ
話がどんどん広がる!やべぇ!十六進法の話もしとこうと思ってたのに無理だこれ!
うん。
桁あふれという概念についてHさせて頂く。
A太郎、B乃介が、フリップフロップくんで足し算をしようと思った。それぞれ四個ずつ持っている。
1111
0011
筆算すればいいんで、A太郎の持っているフリップフロップくんで足し算の結果を表現しようとしたのだが、これ足りてないよね。
計算結果は
0001 0010
になってしまう。フリップフロップくんが5個必要だった。計算した結果の繰り上がりが溢れてしまった。無理やりに計算した結果、A君の持ってるフリップフロップくんはご覧の通り「0010」になってしまった。十進法で言うところの2だ。本当は溢れた分含めて18だったのに。
これが桁あふれ。いま見て頂いたように計算結果が違っちゃうから困った現象なんだけど、逆にこれを利用して「加算すると減算結果が分かる」仕組みを考えてみよっか。イェイイェイ。
▼補数
ぶっちゃけたハナシ「2の補数表現」というアレを利用すんだわ。そしてそれは、もっとぶっちゃけたハナシ「ビット反転して1足す」なんだわ。
理屈は後述するんだが、ものは試しだ。
まず、「-2」を表現したいものとして、「2」を「ビット反転して1足す」してみよう。
0010
反転。
1101
1たす
1110
1110になったな。これ、「14」とビットの並びが被ってしまっている。だから、(ビット演算における)補数表現のルールでは、「正の数」で最上位ビット、すなわち一番左のビットを利用することはできない。悔しい。
つまり、四個のフリップフロップくんで表現できる最大の値は「0111」。十進法でいうところの「7」となる。
じゃ、「7」に今作った「-2」を足してみる。
0111
足す
1110
は
0001 0101
で、溢れは無視されてしまうもんで
0101
すなわち「5」だな。なんか計算できてる。
▼2の補数の理屈
「0000」から「0010」を筆算で引いてみる。
…うわぁ無理じゃん。だから、溢れの部分から借りてこよう。
0001 0000
引く
0000 0010
は
0000 1110
だな。「1110」はさっきも見た通り「2」の「2の補数表現」と一致する。
つまり、「計算した結果溢れるビット」を、事前に借りておくという考え方だ。それは「ビット反転して1を足す」という方法で算出できる。
すごーい。感動した。
2の補数表現の覚え方は「反転して1を足す」で「パンいち」。これ。変換操作を忘れたなら、まずパンイチになればいい。
色々補足できるが、とりあえずCPUで計算するときは基本的に「2の補数表現」が使われているんです。なぜなら、既にある加算回路を使えれば便利だから。エコだから。
「-2 を ひく」も、同じ手順を踏んでやればいいよ。ビット反転して1を足すと「-2」は「2」になってくれる。
◆小数値
これなぁ。めんどくせぇな説明。でも重要なんだよな。
▼十進法における小数の表現
(理解しなくてもいい)
まず、「十進法が真理」という考えを捨てろ。それは洗脳だ。十でも百でも千でもいいけど、それらはキリのいい数字でも何でもない。
俺らの指が偶然にも十本だったから十進法が採用されたのだらしい。諸説あるが。
例えば「1/3」は、十進法では「0.33333333…」と割り切れなくなるだろう。だが、「三進法」であれば「0.1」となる。
うん。大丈夫だ。よしよし。落ち着け。いま説明するから。
▼二進法における小数の表現
二進法なんでゼロとイチでしか小数値を表現できない。じゃあ、以下の二進数は、十進法でいうところの幾つに当たるだろうか。
0.1
はい。
まず、小数の絡まない「”二進法における各桁”が示す十進数」の並びを思い出していただきたい。
... 128 64 32 16 8 4 2 1
これはつまりこうなる。
... 2の7乗 2の6乗 2の5乗 2の4乗 2の3乗 2の2乗 2の1乗 2の0乗
でさぁ、
あ、そうそう!すごい!いやー賢いねぇ君は。君のような勘のいいガキは嫌いだよ。
そうです。小数点以下は、「2のマイナスn乗」になるんですね。マイナスn乗は逆数でありますから、二進法の「0.1」は十進数における「1/2」すなわち「0.5」になるよね。
... 128 64 32 16 8 4 2 1 1/2 1/4 1/8 1/16 1/32 1/64 1/128 ...
これな。
十進法で言うと
0.5 0.25 0.125 0.0625 0.03125 0.015625
つまり、「0.10101」は「0.65625」となる。
えー?じゃあ0.6ってどうやって二進法で表現すればいいのさ?難しくない?
っていう発想からしてそもそも負けてて、十進法典に洗脳されてるんです。
三進法の世界から来たひとは「三進法の0.1」を十進法でどうやって表現すればいいんだよと思ってます。十進法では循環小数にするしかない。つまり「0.333333…」と書くしかないのだ。
二進法においては、十進法の「0.1」がまず循環小数になる。
0.000110011001100110011...
「0011」が循環してんですね。進法によって循環小数は違う。そもそも小数は「単位」というものがあるから(略
▼機械で小数値を扱う方法
フリップフロップくんをまた連れてきました。
フリップフロップくんがONとOFFしか表現し得ない以上、効率的に値を受け渡すためには「1/2」「1/4」「1/8」とオデコに書いていくしかないんだね。
さて、8個のフリップフロップくんがいるわけだが、どのように割り振ろう。「整数部」「小数部」を半分、すなわち 4・4 にわける?
8 4 2 1 1/2 1/4 1/8 1/16
ってしたときに、これまたロスが発生してくる。このフリップフロップくんたちのセットで、できるだけ多様な情報を送りたいのだ。
何を言っているのかっていうと、つまり整数値で言ったら15とちょっとしか送れない。小数のほうをひとつ削って自然数のほうに回せば、31、二倍いける。
16 8 4 2 1 1/2 1/4 1/8
63、127と、倍々で表現できる数が増えるのだ。実際のコンピューターのやり取りでは8bitじゃなくて32bit、64bitをひとまとまりにしてっから、この類のロスはもっと大きなものになる。
▼浮動小数点
だから、「いくつのフロップフロップくんを小数の表現に利用するか」までを、同時に伝えてしまおうぜ?
という発想、「浮動小数点数」という考え方におさまった。
IEEE754のWikipediaみといて。
…っていう風に投げるわけにもいかんので説明する。
次の二進数を読んでみる。
1010.0101
これは十進法で言うと「8 + 2 + 0.25 + 0.625」で「10.3125 」ですな。
じゃあ、こいつの小数点をふわふわ時間してみる。点を、フワフワと右に4つずらしてみよう。
10100101.0
これは十進法で「165」。そいで、「165 ÷ (2の4乗)」は、最初の「10.3125」と一致するんですね。
十進法で小数点をひとつ左にずらすと、「1/10」されるな。それと同じように、二進法だと「1/2」されるのだ。
「10100101.0」の小数点を左に4つ移動させてやれば「1010.0101」になって、「10.3125」すなわち「165 × (2のマイナス4乗)」を表現できる。
…つまり?
「二進数の数の並び」「小数点を何個ずらすか」という情報のセットを伝達すれば、受け取り手のほうで値を作り直すことが出来るよね?
わかる?
※ちょっと「仮数部」について現状に即さない感じに教える。
例えば俺が、8個のフリップフロップくんのグループに「仮数部」という名前を付けて「10100101」を作った。そして4個のフリップフロップくんのグループに「指数部」という名前を付けて「0100」を作った。
これを受け取ったお前は、「指数部」が「4」だから、「10100101」の小数点を左に4つずらしてあげればいいの。そうすりゃ「1010.0101」になるっしょ?お目当ての「10.3125」を伝達できた。やったぁ。
▼IEEE754
IEEE754っつーのは、「ビットのうち幾つを仮数部にして幾つを指数部にする?」という取り決め。あと、MSB(一番左のビット)を符号として扱う事を定めている。
符号ってのはプラスかマイナスかってこと。2の補数表現は小数値で使うメリットがさほど無いんで、「プラスの値かマイナスの値か」は単純に1ビット使って表現される。
▼バイアス値
たとえば「指数部」をマイナスの値にするとどうなるだろうか?
「1010 0101.0」の小数点を「左にマイナスn個」ずらせる。すなわち右にずらすことが出来るだろう。つまり「1 0100 1010.0」を表現できる。これは「330」を表現できる。8個ずらしたら「1010 0101 0000 0000.0」で「42,240」。
こういう風に、指数部をマイナスにできれば、おっきい値も表現できて幸せです。だから指数部にもプラスマイナスの符号が必要になる。
…のではないんですよ。「バイアス」「下駄ばき」という事前処理がなされることになる。
高さ5cmの下駄をはいた人が居ると思えよ。その下駄を履いた状態で身長を測って、180cmだったとする。そのときの本当の身長は?175cm だべ?その理屈を利用して、符号ビット無しでマイナスを表現する。
IEEE754で定義される「単精度浮動小数点数」の指数部は8ビットある。8ビットなんで「1111 1111」で「255」を表現できる。けど、マイナスの値も表現したい。ってなもんで、「127の下駄を履いた後の数値を入れておけや」という取り決めがなされている。
だから、例えば「1111 1110」は、「254 – 127」で「127」を表現する。
「0011 1100」は「60 – 127」で「-67」を表現するんすね。
渡す側は「+127した値を入れてあげる」。貰った側は「-127して扱う」。そういう取り決め。
符号に1ビット費やす必要が無くてエコだし、小数値を機械的に計算をするのに都合がよかったりする。
ただ指数部が「0000 0000」と「1111 1111」である場合の扱いが特殊。この項目を参照しといて。
※重要なこと
「指数部」は小数点を左に何個ずらすかという情報を表現しているとしたが、それは嘘ですよね。大体の感じで読めたかもしれんが、「仮数部を2のn乗する」のnを示している。だから指数部が大きければ値の絶対値は大きくなるし、小さければ小さいほど値の絶対値はゼロに近づいていく。
いうなれば、小数点を右に何個ずらすかという情報を表現している。
「2のn乗」すれば、二進数値では小数点が右にn個ズレるよね。
▼仮数部と正規化
仮数部に入る値は実は小数部分だけなんです。
何のことかっていうとだな。
「1.0101 1010」って値があったときの、小数部「0101 1010」が入るんですね。
おまえ「えっ。整数部の1は?」
省略しました🤗。整数部は、ほぼ全ての値で「1」で固定できるから。
さっきの二進数「1.0101 1010」を2倍すると「10.1011 0100」になるじゃん?逆に、1/2倍すると「0.1010 1101」になるじゃん。その「2倍」とか「1/2倍」って情報は、指数部に持っていけるじゃん。
だから、指数をうまいこと調節してやれば、ほぼ全部の値を
1.xxxx xxxx xxxx
みたいに「整数部が1だけ」の形にすることが出来るんすね。(全部じゃない)
だから、この整数部に残った「1」を省略することができる。これを「けち表現」という。だから、仮数を知りたい時は「1.」を左につけてやれ。
うん。これで浮動小数値の説明は終わり。面倒くさかったでしょう。できる限り効率よく値を表現しようとしてこんな感じになってしまったんですね。
▽余談:誤差と丸め
「Excelで小数値を計算したら結果ずれたわ」という事があったかもしれないんだけど、そういう事だったんです。「0.1」すら二進法では循環小数になっちまう。だから正確な値を覚えておくことが出来ない。ある程度のところで丸められる。だから計算結果がずれる。
他にもずれる理由あるんだけど割愛。
しかし最近のExcelは計算結果がズレにくいようになってる気がする。気のせいだろうか。ズレるのはズレるんだけども、昔よりも賢くなってる気がする。
▽余談:Decimal
decimalっていう型を用意している言語とかDBがある。ズレづらい。ズレる時はズレる。
▽余談:GPU
浮動小数点数の計算はコストが高い。ALUでやろうとすると加算でも50クロックかかったりなんてザラ。調べたら40クロックとかって言われてた。(参考)
SIMD命令で高速化ワンチャンっていう話もあるけど、しかし浮動小数点数を扱わないようにデータを設計するのが高速化のキモではある。でも使わないとしゃあない場面もそりゃある。
GPUってあんじゃん。画面の描画って浮動小数点数計算とか掛け算とか割り算とかサインコサインタンジェントとか沢山やらないといけないんだけど、言ったように計算のコストがなかなか高い。頑張ればCPU単体でも画面の描画できるんだけど、その計算が割と辛いから、特化したGPUに任せてるんですね。特化、つまりチューリングマシンとしての動作を期待されないGPUは非ノイマン型になっちまったわけだけども、機能を制限したからこそAIやマイニングの処理を高速化できるポテンシャルをもってるんですね。
※お前が気になりそうな言葉を羅列しておいたから、あとは自分でググれや。
Intel CoreっていうCPUにはオンダイでGPUが載っているんだけど、AMDのCPUであるところのRyzenは(略
※Ryzen3 2200Gからオンボードグラフィックス積んでるCPUも発表し始めてる。
◇余談:手でモノを数える時
手の指が10本あるな。そしてここにミカンが沢山ある。
さて、その10本の指で幾つまで数えられる?
じゅっ…10!?おおおお前!お前は何を聞いてたんだ!お前!ケツ出せ!
うわきったね!汚ねぇケツ!(ペチーン)
ケツしまえ!
っていうので、「210 – 1」 、1023まで数えられますね。指をビットとして捉えればよろしい。不器用な人では無理かもしれないが。あと人によって多かったり少なかったりするわけだけど、一本多かった豊臣秀吉なら「211 – 1」で2047まで、フォードなら「212 – 1」で4095まで数えられるんですね。約二倍。すげぇお得。
n個のビットで数えられる自然数は「 2n – 1 」です。
あと、グラビティフォールズは面白いし泣けるので観ろ。
◇余談:量子コンピューター
説明した二進数の話は、量子コンピューターが主流になったらアレがアレする。量子ビットはゼロとイチだけじゃないから。だから量子コンピューターをご家庭に届けるためには、それ用のカーネルを書かないといけない。Linux(UNIX)はC言語とちょっとしたアセンブラで書かれているんだけど、そもそもその命令セットが量子命令セットとでも呼ぶべき(略
誰か俺に量子コンピュータについて丁寧に説明してくれないだろうか。お前ら説明下手なんだよ。このへんはまぁまぁ分かりやすかったけど。でも量子の測定のヤバさについて説明できてないよね。りょ、量子デコヒーレンス!?量子消しゴム!?えーもう量子ってなんなの!?量子もつれがERP相関でコペンハーゲン解釈なの!?そこに繋がってくるの!?そんなよくわかんないものでコンピュータ作って良いの!?やっヴェ!!吐きそう!!
的なね。だから低レイヤの勉強をしよう。つまりプログラマーを続けたいんなら量子力学の勉強をしておけ。なんかおかしいこと言ってるけど。
◆以上
書くのつかれた。そんな感じです。
このレイヤの下に半導体の世界がある。お前らが開発で、コード書いてメシを食えてんのは、例えるなら蛇口をひねったら水が出てきてよっしゃラッキーみたいな感じだ。バイポーラトランジスタやらダイオードやらで遊ぼう。江崎ダイオード。江崎ダイオードはヤバい。トンネルしててすごい。使われてるとこ見た事ないけど。
うん。「わかんねぇ」とか「間違えてるよ」とかあったらコメントしてくれな。機嫌がよかったら記事を修正しておく。
じゃあな!!
コメントを残す