二進数
二進数は 2 を基数とする位取りで整数を表す。符号なし 8 ビット語の重みは左から 128、64、32、16、8、4、2、1 で、0–255 を表せる。
各桁に 0 と 1 しかない走行距離計を考えるとよい。下の桁が満杯なら 0 に戻して左へ繰り上げる。最上位も満杯なら、桁上がりを受ける九つ目の桁はない。8 ビットだけを残すとゼロへ戻る。本課では 77 の表し方と、符号なし 8 ビットの折り返しで 255+1 が 0 になる理由を追う。
二進数の仕組み
重みは位置で決まる。右端を 0 とし、位置 k は 2^k。ビット 0 は 1、ビット 3 は 8、ビット 7 は 128 の重みを持つ。1 の列の重みを加え、0 の列は加えない。
01001101 はビット 0、2、3、6 が 1 なので、1+4+8+64=77 となる。
数をビットへ変えるには奇偶で最下位を取り出す。ビット 0 以外の重みはすべて偶数なので、n % 2 が最下位ビットを与える。n // 2 でそのビットを捨て、残りを一位置右へずらす。n が 0 になるまで繰り返す。余りは下位から出るため、紙の左から右への表記順とは逆になる。
1 を足すとき、最下位が 0 なら 1 にして終える。1 なら 0 に戻し、左へ桁上がりを送る。連続する 1 を消し、最初の 0 に到達して止まる。01111111 + 1 は 8 ビットを書き換えるが、01110000 + 1 は 1 ビットだけだ。
ステップ実行
既定値 n = 77 を 8 ビットへ変える。全ビットを 0 で初期化し、右から埋める。
77 % 2 = 1なのでビット 0 は 1。77 // 2 = 38。38 % 2 = 0なのでビット 1 は 0。38 // 2 = 19。- 19 は奇数なのでビット 2 は 1、商は 9。
- 9 は奇数なのでビット 3 は 1、商は 4。
- 4 は偶数なのでビット 4 は 0、商は 2。
- 2 は偶数なのでビット 5 は 0、商は 1。
- 1 は奇数なのでビット 6 は 1、商は 0。ここで終える。
ビット 7 は訪問しておらず、初期値 0 が残る。結果 01001101 を読み戻せば 64+8+4+1=77 となる。
Decode に切り替えると逆の手順を追う。ビット 0 で 1 を加え、1 では加えず、2 で 4、3 で 8 を加える。4、5 は 0、6 で 64 を加え、7 は 0。最終値は 77 で、二つの変換を相互確認できる。
Count は 13(00001101)から 4 回増やす。
- 13→14:ビット 0 を消し、桁上がりをビット 1 へ置く。
00001110、変更は 2 ビット。 - 14→15:最下位を 1 にし、
00001111。変更は 1 ビット。 - 15→16:ビット 0–3 を消し、ビット 4 を立てる。
00010000、変更は 5 ビット。 - 16→17:最下位だけを 1 にする。
セルが語を表し、上のラベルが重み、下のカーソルが現在の読み書き位置を示す。台帳には除算、重みの加算、インクリメントが記録され、計算とビットを照合できる。
8 ビットが 255 で終わる理由
8 個の位置に二つずつ選択肢があり、パターン数は 2⁸=256。0 から数えた 256 個の値の最後は 255 だ。11111111 は 128+64+32+16+8+4+2+1=255 で、256 に 1 足りない。
ここへ 1 を加えると全桁が 0 になり、キャリーはビット 7 の外へ出る。九つ目の桁がないので捨て、00000000 を残す。Odometer wrap は 253 から 254、255 と進み、ゼロへの折り返しでデモを終える。語自体はその後も数えられる。
固定幅の符号なし折り返しは、カウンターのリセット、時刻の巻き戻り、レベル値の異常などにつながる。幅を広げても境界は移るだけだ。折り返しが許されなければ増加前に上限を確認するか、検査付き演算を使う。下位ビットだけでは失ったキャリーを報告しないが、ハードウェアのフラグや検査 API は検出できる。
計算量
W ビットの変換は最大 W ビットを訪ねるので O(W)。固定の 8 ビットなら定数上限となる。インクリメントはキャリーが全ビットを通ると最悪 O(W)。全 256 開始パターンで平均すると、反転数は一回あたり 2 ビット弱だ。
境界ケース
n = 0は変換ループへ入らず、初期値00000000を保つ。n = 255は除算 8 回で全位置を書く。- 2 のべき乗は 1 が一つだけ。64 は
2⁶なのでビット 6 だけが立つ。 00000000のデコードはエラーではなく 0。- 255 を越えた 8 ビットカウントは 0 に戻り、パターン自体には警告がない。
よくある間違い
- 余りの生成順に左から書く。低位が先に出るが、表記の左は高位だ。
01001101を反転した10110010は 77 ではなく 178。 - 先頭のゼロも計算したと思う。77 の除算は 7 回で、ビット 7 は初期値だ。文字列を構築する実装なら幅までゼロ埋めする。
- 左端をビット 0 と数える。左はビット 7、右が 0。
- この 8 ビット語に 256 を保存できると思う。数学的な和は 256 でも、下位 8 ビットは 0。
- 反転数を数値の増分と混同する。15→16 は 5 ビット、14→15 は 1 ビットを変えるが、どちらも数学的には 1 増える。境界の折り返しは法 256 で読む。
モデルの範囲
位取り、変換、キャリーを一列で追うため符号なし 8 ビットに固定する。実機には 16、32、64 ビットなどもあり、境界での言語動作は異なる。Python 整数は拡張され、固定幅では折り返さない。C の符号なし整数は型の幅で剰余を取り、Rust はデバッグ時に整数オーバーフローを検査して panic し得る。明示的な wrapping 演算で折り返しも指定できる。Rust のオーバーフロー規則を参照。C の符号付きオーバーフローは未定義動作だ。負数には追加の符号化規約が必要で、二の補数のレッスンで扱う。ここでのモデルは機械算術の全規則ではない。
続けて学ぶ
同じ手法につながるレッスンや、次の概念への入口です。
鍵マークのあるレッスンの操作には有料アクセスが必要です。解説はすべて無料で読めます。
誤りや気になる点を見つけましたか? フィードバックを送る。