10進数→2進数変換のアルゴリズムで出力を逆に読む理由
概要
手計算で10進数を2進数に手計算で変換するアルゴリズムとして,筆算を連鎖したあと,剰余を逆順に並べて出力とするものがありますが,なぜ逆順でうまくいくのか考えたことがなかったので,メモがてらまとめてみます.
概要としては,
- ビットマスクとビットシフト
- 数式
の両面から納得することができました.
アルゴリズム
10進数表記の数 を2進数表記するとして,この記事で対象にするアルゴリズムは以下のとおりです.
- を2で割り,剰余と商を記録する
- に を代入し,が2を下回るまで,これを繰り返す
- 剰余を逆順に左から並べる
の場合を例に図解すると以下のようになります.
Python(3.6.2)のコード例は次のようになります.(実行例)
bits_list = [] while a > 0: bits_list.append(a % 2) a = a // 2 bits_list.reverse()
この記事では,割り算の方向に対して出力の読み上げが逆方向に行われる理由について考えていきます.
準備
2進数で桁の数は,式のように表されます.
は,番目の桁の値を表し,またはです. 例えば,の場合は,,,,となります.
もちろん,2進数,10進数は同じ数の違う表現というだけなので,
が成り立ちます.
(左辺:10進数表現,右辺:2進数表現)
ビットマスクとビットシフト
このアルゴリズムの要点は,2で割った場合の商と剰余を使い続けることにあります.ここで,2で割ることと2で割った場合の剰余をビット演算で表現することを考えます. このとき,2で割ることは 右に1bitシフトすること に,2で割った場合の剰余は 0b1との論理積 に対応します.
2で割った場合の剰余について
右シフトで商が得られることを用いると,ある数bについて2で割った剰余は a - (a>>1)<<1で求められます.
(正の数において,シフト時に足りないbitは0が補われるため)
ここで,最下桁以外の各桁の値はaと(a>>1)<<1の間で必ず等しくなるので,結果として0b1との論理積と同義です.
剰余を求めて記録し,2で割る操作は,まるで だるま落としのように,最下層の桁を取り出し,桁を落とす操作に対応します. したがって,最下桁が最初に取り出され,最上位桁が最後まで残るため,逆順に出力されます.
ビット演算を用いて前述のコードを書き直すと,次のようになります.(実行例)
bits_list = [] while a > 0: bits_list.append(a & 0x1) a = a >> 1 bits_list.reverse()
数式
2で割った場合の商と剰余を求めることは,数式から2をくくりだして, の形式にすることと同義です. そこで,
を変形すると,
となります.ここで,剰余 を無視して(アルゴリズム上は記録することになりますが), この商の部分に着目すると,再び2でくくることができます. したがって,
となります.
このように,剰余の項を無視して2でくくりだす,を繰り返すことができ,これが 最下桁の値から 剰余として分離することに相当するため,出力を逆順に読むことになります.
まとめ
10進数→2進数変換アルゴリズムの出力を逆順に読み上げる理由について,
- ビット演算を用いてアルゴリズム的に
- 数式を用いて理論的に
意味付けを行いました.
補足
# start def convert_to_bin(n, acc_list=[]): return reversed(acc_list) if n == 0 else convert_to_bin(n // 2, acc_list + [n % 2]) # end a = 29 result = "".join([str(b) for b in convert_to_bin(a)]) print(result)