高度なプログラミングの根底には「数学」があります。検索アルゴリズム、ゲームの物理エンジン、AIといったプログラムの良し悪しは数学を理解しているかどうかで決まります。新刊『 プログラミングのための数学大全 』から「コンピューターはどのように数値を扱うのか」を抜粋して紹介します。第1回は、基本中の基本である「基数とは何か」です。
問題です。100より大きくて100に2を足した数よりも小さい整数は、なんでしょう? なぞなぞとしては、あまり上等とは言えませんね。子どもの算数のテストになんとか使える程度です。
お察しのとおり答えは、101=100+1=1×102+0×101+1×100です。私たちがふだん目にする数字は、基数(1桁で使える数字の種類)が10、または10を底(基準となる数)とする数であり、10の累乗の倍数で表される10進数です。じつは101は、「100の桁の100と10の桁の0と1の桁の1を足した数」を短縮した表現なのです。
説明するまでもなく、私たちは10進数の書き方には慣れています。ではちょっと基本に戻って、任意の基数Bの数値が何を意味するかを考えてみましょう。
ここでは、d∈[0,B-1]となります。∈はdが次の集合の元であることを示す記号です。B=8ならば、dは[0,7]={0,1,2,3,4,5,6,7}のいずれかの数になります。dは桁を表します。左に進むごとに桁が1つずつ上がり、桁数はBの指数となります。
上の式を右に伸ばすことも可能ですが、その場合はBの指数が1つずつ減ります。
したがって、1234.567という文字列も、基数によって解釈が変わります。私たちは10進数に慣れているため、文字列を見ると自然に基数は10だと思い、これを「千二百三十四、点、五六七」と読んでしまいます。しかしこの文字列のなかで最大の数は7なので、7よりも大きいBを底とする数という可能性もあります。混乱を避けるために、ここでは基数10以外は、添字(そえじ)で基数を書き加えることにします。
数は、それぞれの基数で使える範囲の値で書き表すことになっています。小数点(より広い意味では「基数点」)を使えば、もっと桁を増やせます。Bを底とすると、[0,B-1]の範囲の数値が使えます。
人類は10を底としたときの、[0,9]の範囲で使える数の記号を生み出しました。この記号はほかの基数でも使えますが、B>10のときはどうしましょう。基数が10、11、12となったときに、どんな記号を使えばいいでしょうか。どんな記号を当てはめても、それは自由です。古代バビロニアでは、基数60で数を数えていました(60進法)。1の位と10の位を表す記号を組み合わせて表記します。60進数というだけでとんでもなく面倒に感じますね。掛け算表がどれだけ巨大になるか想像してみてください。
プログラミングでは10以外の基数も使います。代表的なものは、2と8と16です。基数B=16では、10から15を表す別の記号が必要ですが、私たちはAからFまでのアルファベットを充てています。小文字でも大文字でも構いません。では、プログラミングでよく目にするこれらの基数を説明しましょう。
2進数、8進数、16進数
基数2はコンピューターの共通語「2進数(バイナリー数)」です。B=2で使える数は[0,1]となります。そのため2進数整数は、2の累乗数を加算したものに過ぎません。例を示します。
以下も同様です。
明示的に基数点が入った2進数はあまり一般的ではありませんが、本章の後半(連載第2回)で説明する浮動小数点数をコンピューターが扱う際にはとても重要です。
Pythonで2進数を扱う例を見てみましょう。
0bというプレフィックス[接頭辞]は、2進定数を示しています。これはまた、本書でも触れているGNU Cのコンパイラーgccでも通用します。面白いことに、Cにはそもそも2進数値をプリントする機能がありません。これを使って、最初の実習とまいりましょう。
基数8の数は8進数です。この数十年の間に8進数はほとんど使われなくなりましたが、先に例としてお見せした1234.5678は8進数です。Pythonではプレフィックス0o[ゼロの後に英字の小文字o]、oct関数、または書式指定子%oを使います。
Cコンパイラーは、0で始まる数値は8進数と見なし、printfの使用時には%oの書式指定子と解釈します。おかしなことにPythonも0で始まる数値は8進数だとわかるのですが、処理を行う代わりに文句をつけてきます。
そして、基数16の数値は「16進数」です。「ヘックス」とも呼びます。ヘックスは8進数よりもずっと一般的です。PythonもCも、それぞれ0xプレフィックスと%x書式指定子でヘックスが読めるようになります。
Pythonのint関数では、[2,36]の基数をもうひとつの引数として渡して、0から9まで、AからZまでの符号を認識させることができます。つまり、11011という文字列がいろいろに解釈できるということです。
intは、単に基数10の文字列を認識するだけでなく、もっと柔軟に利用できることを覚えておくといいでしょう。
基数の変換
プログラミングでは、2、8、10、16の間で基数を変換する作業がよくあります。ここでは基数10はとりあえず忘れましょう。10は、けっこう厄介な基数なのです。私たちの遠い先祖の足の指が6本だったなら、基数12が標準だったでしょう。12進数のほうが優れていると主張する人は少なくありません。ちなみに私の好みは基数6です。
基数の2と8と16の間の変換は、どれも2の累乗なので簡単です。基数2を基数8に変換したいときは、右から左へ3桁ずつに分けて、それぞれのグループを基数8の数に置き換えます。
同様に、基数16では4桁のグループに分けます。
この方法は、双方の基数が2の累乗なら、どの基数にも使えます。基数32がお好きなら、5桁のグループに分けてください(25=32)。
8進数をヘックスに、またはその逆に変換したいときは、基数2で前の手順を逆に行うのがもっとも早いことを私は発見しました。
前にも述べたとおり、10進数を2進数に変えるのも簡単です。2進数さえあれば、8進数もヘックスも思いのままです。アルゴリズムは次のとおりです。
10進数の数値を2で割り、商と余りを記録します。その商を2で割って、商と余りを記録します。これを商が0になるまで繰り返します。その余りを逆の順番に並べたものが、この数の2進数版となります。
たとえば、359を2進数に変換してみましょう。アルゴリズムに従うと次のようになります。
余りを下から上に読むと、1011001112=359となります。
このアルゴリズムが成立する理由を解説します。n=b4 b3 b2 b1 b0という5桁の2進数を思い浮かべてください。bは2進数字、つまり1または0です。これを展開すると次のようになります。
ここではあえて数の順番を逆にしています。さらに共通因子の2でくくります。
nを2で割ると、余りがb0、商がq=b1+b2×21+b3×22+b4×23となります。n=b0+2qだからです。さらに2で割ると、新たな余りとしてb1が得られ、残りの項は2の指数が1ずつ減った商が得られます。続けてb2、b3と処理を進め、すべての2進数が割り出されて商が0になるまで繰り返します。こうして最初(b0)から最後(この場合はb4)まで2進数を取得して並べると、その数の2進数を逆順にしたものになります。
反対に、基数Bを基数10に変換する場合はどうでしょう。手計算で行うなら、位取りの記数法の定義に従い各値を10進数に変換して合計します。したがって、たとえばFC5816は次のようになります。
10進数を2進数に変換するアルゴリズムは、すべての基数に使えます。ただ基数で割って、余りをその基数の値として記述するだけです。たとえばPythonならリスト1-1のようになるでしょう。
簡単なテストでdec2baseが機能することがわかります。
dec2base関数では、10以上の数には大文字アルファベットを使うことで、[2,36]のあらゆる基数に対応できます。chr関数は、指定されたASCIIコード(情報交換用米国標準コード)に対応する文字列を返します。
0から9は、ASCII48から57です。10以上は、ASCII65(A)から始まる大文字アルファベットを使用します。ここでは、rは少なくとも10以上とわかっているので、オフセットは65ではなく55です。dec2baseとintを使えば、10進数を介してあらゆる基数同士の変換が可能になります。
数学とプログラミングには深い結びつきがあります。本書を読めば、基本的なコーディングから本格的なソフトウェア開発までに欠かせない「数学全般」が一気に学べます。
ロナルド・T・クナイスル(著)、井上卓馬(監訳)、金井哲夫(翻訳)/日経BP/5500円(税込み)




















