16 進数から 10 進数への変換
2 進数 24 桁は、目で追えない
111111110110011000000000 を渡されて、どこまでが何なのか読める人はいません。桁が長すぎて、数えている途中で位置を見失います。
ところが 4 桁ずつ区切ると、様子が変わります。
プレーンテキスト
1111 1111 0110 0110 0000 0000
F F 6 6 0 0#FF6600 になりました。CSS で見かけるあの記法です。16 進数は、2 進数を 4 桁ずつまとめて 1 文字に置き換えたものにすぎません。
4 桁で 16 通りだから、1 文字に収まる
2 進数 4 桁で表せるのは 0000 から 1111 までの 16 通りです。16 進数はちょうど 16 種類の文字を使うので、4 桁と 1 文字が一対一で対応します。数字は 10 種類しかないので、足りない 6 つを A から F が引き受けて 10 から 15 を担当します。
区切りがずれないのも大きな利点です。1 バイトは 8 桁なので必ず 2 文字、4 バイトなら 8 文字です。メモリの中身やパケットを 16 進で並べると、バイトの境目が文字の位置にそのまま出るので、目で数えられます。
重みは 16 倍ずつ増える
10 進数に直すときは、また桁の重みを使います。ここも考え方は変わらず、右端から 1、16、256 と 16 倍ずつ増えていきます。2 進数を 4 桁まとめたのだから、重みも 2 の 4 乗ずつ増える、と考えても同じです。
1A3 なら次のようになります。
プレーンテキスト
1 * 256 = 256
A * 16 = 160 A は 10 として計算する
3 * 1 = 3
合計 419文字の A は、数の 10 ではない
ここが実装でいちばん詰まるところです。受け取るのは文字列なので、A は A という文字でしかありません。数の 10 として計算に混ぜるには、文字ごとに置き換える手当てが要ります。
そして同じことが数字にも起きます。文字の 7 も、数の 7 ではありません。文字のまま足すと、多くの言語で数にならず、つながった文字列が返ってきます。0 から 9 の側にも置き換えが要る、と気づけるかどうかで詰まる時間が変わります。
もう 1 つの落とし穴が大文字と小文字です。ff も FF も Ff も、同じ 255 を指します。片方だけを見て分岐すると、もう片方が黙って落ちます。どちらかに寄せてから処理するのが安全です。
要件
- 0-9、A-F、a-f のみからなる文字列と仮定して良い (0x プレフィックスはない)
- 組み込みの基数変換 (int(s, 16) / parseInt(s, 16) / Integer.parseInt(s, 16)) は使わない
- 大文字小文字どちらも処理できること
入出力例
hexToDec("FF") → 255
hexToDec("0") → 0
hexToDec("1A3") → 419
hexToDec("ff") → 255
hexToDec("100") → 256
hexToDec("ABCDEF") → 11259375
hexToDec("10") → 16