数と論理

INPUT · スライド

2進数と16進数

01 / 12

ずっと気になっていた2つのこと

CSS で色を指定したとき、#ff0000 と書いて赤になった。なぜ ff が赤なのか、説明されないまま使ってきたはずだ。

JavaScript で 0.1 + 0.2 を計算したときには、0.30000000000000004 という気味の悪い答えが返ってきた。電卓なら間違えないのに、コンピュータのほうが計算を間違えた。

このレッスンは、この2つにきちんと答えを出すためにある。 どちらも原因は同じで、機械が数を 0 と 1 だけで置いているからだよ。そこを一度のぞいておけば、色も誤差も、もう不思議ではなくなる。

02 / 12

なぜ 0 と 1 なのか

コンピュータの中身は、電気が流れているか、いないかしかない。だから使える数字は 0 と 1 の2つだけで、これを 2進数 と呼ぶ。10種類の数字を使う私たちの数え方が 10進数 だよ。

数え方が違っても、桁の意味は同じ仕組みでできている。10進数の 305 は、左から 100 の位・10 の位・1 の位。右へ1つ動くたびに 10で割った重みになるよね。

2進数はこれが 2で割った重みになるだけ。右から 1、2、4、8、16… と倍々に増えていく。この桁ごとの重みさえ押さえれば、何進数でも同じやり方で読めるよ。

10進 305 3×100 + 0×10 + 5×12進 1101 1×8 + 1×4 + 0×2 + 1×1 = 13

03 / 12

10進数を2進数にする

10進数から2進数に直す手順は1つだけ覚えればいい。2 で割って余りを書き、商をまた2で割る。0 になるまで続けて、余りを下から読む。

13 でやってみよう。13 ÷ 2 は商 6 の余り 1。次に 6 ÷ 2 は商 3 の余り 0。3 ÷ 2 は商 1 の余り 1。1 ÷ 2 は商 0 の余り 1。ここで商が 0 になったので終わり。

余りを下から上へ読むと 1101。前のスライドで確かめたとおり、1101 は 8 + 4 + 1 = 13 だよ。上から読むと逆さまになるので、そこだけ気をつけよう。

13 ÷ 2 = 6 余り 1 ↑ 6 ÷ 2 = 3 余り 0 | 3 ÷ 2 = 1 余り 1 | 1 ÷ 2 = 0 余り 1 |下から読んで 1101

04 / 12

8進数 ― 3ビットで1桁

2進数は正しいけれど、11010110 のように長くて読みにくい。そこでまとめて短く書くやり方が使われる。その1つが 8進数 だよ。

8 は 2 を3回かけた数(2 × 2 × 2)なので、2進数3桁がちょうど8進数1桁になる。だから変換に計算はいらない。右から3ビットずつ区切って、各かたまりを 0〜7 に読みかえるだけ

011010110 なら 011 010 110 の3つに切って、それぞれ 3、2、6。つなげて 326 だよ。

この書き方は今も現役で、Linux のファイル権限(読み取り・書き込み・実行の3つ)を 755 のように書くのは、まさに3ビットを1桁にまとめているからだよ。

2進 011 010 110     ↓   ↓   ↓8進   3   2   6= 326(8進数)

05 / 12

16進数 ― `#ff0000` の答え

もっとよく使われるのが 16進数 だよ。16 は 2 を4回かけた数なので、2進数4桁(4ビット)がちょうど16進数1桁になる。

0〜9 では 10 種類しか足りないので、10 から 15 までを a b c d e f で表す。f は 15、つまり 4ビットが全部 1 の状態だよ。

ここで #ff0000 に戻ろう。色は赤・緑・青をそれぞれ 8ビット(= 16進2桁)で表している。ff1111 1111 で 255、つまりその色を目いっぱいという意味。だから #ff0000 は赤だけ最大で緑と青が 0 ― まっ赤になるんだ。

8ビット = 16進2桁という対応が、色がなぜ2桁ずつ3組なのかの答えだよ。

ff0000 を4ビットずつf    f    0    01111 1111 0000 0000赤 255 / 緑 0 / 青 0

06 / 12

ビットとバイト、そして k・M・G

0 か 1 を1つ置ける場所を 1ビット と呼ぶ。n ビットあれば 2 の n 乗通りを表せるよ。1ビットで2通り、4ビットで16通り、8ビットで256通り。

この 8ビットをひとまとめにしたものが 1バイト だよ。

大きな数につく k・M・G は、2進数の世界では 1,000 倍ではなく 1,024 倍(2 の10乗)で数えることが多い。2 の20乗が約100万で 1M、2 の30乗が約10億で 1G だよ。

ただし問題文が「1M バイト = 1,000k バイト」と書いていたらそちらに従うこと。どちらで数えるかは問題が指定してくるので、勝手に決めないのが大事だよ。

2^10 = 1,024  ≒ 1k2^20 = 約100万 ≒ 1M2^30 = 約10億  ≒ 1G1バイト = 8ビット

07 / 12

2進数の小数

小数点より下も、考え方は整数と変わらない。右へ1桁動くたびに重みが半分になるだけだよ。1つ目が 0.5、2つ目が 0.25、3つ目が 0.125、4つ目が 0.0625。

だから 0.1101(2進)は 0.5 + 0.25 + 0.0625 = 0.8125 になる。

逆向きも見ておこう。10進の 5.625 を2進にすると、整数部の 5 は 4 + 1 なので 101。小数部の 0.625 は 0.5 + 0.125 なので、小数点以下が .101。合わせて 101.101 だよ。

ここで大事な事実が1つ。2進数で表せる小数は、分母が 2 のべき乗のものだけ0.50.25 はぴったり表せるけれど、0.1 は表せない。10進で 1 ÷ 3 が 0.333… と終わらないのと同じで、0.1 は2進では無限に続いてしまうんだ。

0.1101(2進)1×0.5    = 0.51×0.25   = 0.250×0.125  = 01×0.0625 = 0.0625合計       0.8125

08 / 12

マイナスをどう置くか ― 2の補数

0 と 1 しかない場所に、マイナス記号は書けない。そこで先頭のビットを符号のしるしにする(0 なら正、1 なら負)。問題は、負の数の中身をどう決めるかだよ。

素直なのは 1の補数全部のビットをひっくり返すやり方。50000 0101 なら、-51111 1010。単純だけれど困ったことがあって、0000 00001111 1111両方が 0 を表してしまう(プラスのゼロとマイナスのゼロ)。

そこで実際に使われるのが 2の補数ひっくり返してから 1 を足すやり方だよ。-51111 1011 になる。

これを使う理由ははっきりしている。足し算の回路だけで引き算ができるんだ。5-5 を筆算で足すと、あふれた桁を捨てればちゃんと 0000 0000 になる。ゼロも1つだけ。だから機械はこちらを選んでいるよ。

5  = 0000 0101反転 1111 1010 ←1の補数+1   1111 1011 ←2の補数 0000 0101  (5)+1111 1011 (-5) 0000 0000  (0)

09 / 12

固定小数点と浮動小数点

小数を置く方法は2つある。1つ目が 固定小数点小数点の位置をあらかじめ決め打ちするやり方だよ。「8ビットのうち上4ビットが整数、下4ビットが小数」と決めてしまえば、あとは並べるだけ。単純で速いけれど、表せる範囲がとても狭い。

2つ目が 浮動小数点小数点の位置も一緒に持つやり方。数を m × r^e の形で置き、m を仮数、r を基数、e を指数と呼ぶ。基数は2進数の世界なのでふつう 2 だよ。

指数を変えれば小数点は自由に動くので、とても大きな数も小さな数も同じ大きさの箱に入る。JavaScript の数もこれだよ。

ただし置き方が1通りに決まらない問題がある(0.101 × 2^30.0101 × 2^4 は同じ値)。そこで 仮数部の先頭が 0 にならないように桁をそろえる決まりがあり、これを 正規化 と呼ぶよ。

値 = m × r^em 仮数部r 基数(2 のことが多い)e 指数部正規化: 0.0101 → 0.101        指数を1つ減らす

10 / 12

`0.1 + 0.2` の答え合わせ

いよいよ2つ目の宿題だよ。

浮動小数点の箱の大きさは決まっている。仮数部に入る桁数には限りがあるので、無限に続く数はどこかで打ち切って入れるしかない。

さっき見たとおり、0.10.2 も2進では割り切れず無限に続く。だから機械の中に入っている時点で、どちらもほんの少しずれた近い値なんだ。ずれた2つを足せば、答えもずれる。それが 0.30000000000000004 の正体だよ。

この「桁数の限りのせいで、入りきらない下の部分を四捨五入・切上げ・切捨てして生じるずれ」を 丸め誤差 と呼ぶ。バグではなく、有限の箱に無限の小数を入れる以上、避けられないものだよ。

だからお金の計算に浮動小数点をそのまま使わない。整数(円やセント単位)で持つのが定石だよ。

0.1 → 2進では割り切れない0.2 → 2進では割り切れない近いところで打ち切って足す→ 0.30000000000000004

11 / 12

ほかの3つの誤差

試験では、丸め誤差とまぎらわしい誤差が並べて出される。どんな計算で起きるかで見分けよう。

  • 桁落ち値がとても近い数どうしを引くと起きる。上の桁が消し合って、残るのは下のわずかな桁だけ。有効な桁数が一気に減る
  • 情報落ち絶対値の差が大きい数どうしを足し引きすると起きる。桁をそろえた時点で小さいほうの下の桁が押し出されて消える
  • 打切り誤差 … 本来は終わりなく続く計算を、途中で打ち切ったために生じるずれ。円周率を何桁かで止めるようなもの

見分け方は短い。引き算で桁が減れば桁落ち、大小差で小さいほうが消えれば情報落ち、計算をやめたから打切り誤差だよ。

桁落ち(有効数字が減る) 1.2346 - 1.2345 = 0.0001 → 有効2桁が1桁に情報落ち(小さい方が消える) 1.0e10 + 1.0e-10 = 1.0e10 のまま

12 / 12

ずれの大きさをどう測るか

最後に、誤差そのものの測り方を2つ。

  • 絶対誤差真の値と近似値の差。そのままの引き算だよ
  • 相対誤差絶対誤差を真の値で割ったもの。もとの数に対して何割ずれているかを見る

この2つは順番が入れかわることがある。真の値 1.02 を 1 とすると差は 0.02、真の値 5.05 を 5 とすると差は 0.05。差だけ見れば前者のほうが小さい。

でも割合で見ると、0.02 ÷ 1.02 は約 0.0196、0.05 ÷ 5.05 は約 0.0099 で、後者のほうがずっと正確だよ。

1 円のものを 1.02 円と言い間違えるのと、100 万円のものを 100 万 2 円と言い間違えるのでは、ひどさがまるで違う。割合で見るのが相対誤差だよ。

真の値 1.02 / 近似 1 絶対誤差 = 0.02 相対誤差 = 0.02÷1.02          ≒ 0.0196真の値 5.05 / 近似 5 絶対誤差 = 0.05 相対誤差 = 0.05÷5.05          ≒ 0.0099