[Python]N進数から10進数に変換する方法と、10進数からN進数に変換を自前実装してみる

8進数、16進数、36進数…などの値を10進数に変換します。
ついでに、N進数とは何か振り返り、10進数→N進数変換を自前実装してみます。

N進数→10進数に変換

とてもシンプルに書けます。

int(string, n)

string : 変換したい値
n : 変換したい値が何進数か

確認

>>> int('10', 2) # 2進数 => 10進数
2
>>> int('17', 8) # 8進数 => 10進数
15
>>> int('z', 36) # 36進数 => 10進数
35
>>> int('18', 8) # 変換できない値だとエラーを返します
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 8: '18'

そもそもN進数とは?

ふんわりと振り返ります。

N進数とは、Nを基数として表現した数値、言い換えると、N個の文字を使用して表現した数値です。

具体例として以下があります。

  • 0から9の10個の数字を使うのが、普段使用している10進数。
  • 0から1の2個の数字を使うと2進数。
  • 0からアルファベットのFまでの数字+文字を使うと16進数。
  • 0からアルファベットのZまでの数字+文字(10文字+26文字)で36進数。

なお、2,8,10,16,36のように偶数である必要はなく、例えば7進法なら0〜6の7個の数字を使えばできます。
また、アルファベットは大文字・小文字を使い分けると62進法(10+26+26)となります。

10進数→N進数への変換

上記踏まえて、せっかくなので自前で10進数→N進数への変換を実装してみます。

10進数→2進数への変換

# n_ary.py

def base2(value): # 10 => 2
    try:
        tmp = int(value)
    except:
        raise ValueError('Invalid value:', value)

    # nを2でわり、あまりが0になるまでわり続け、あまりを配列に加えていく
    result = ''
    tmp = int(value)
    while tmp >= 2:
        result = str(tmp%2) + result
        tmp = int(tmp / 2)
    result = str(tmp%2) + result
    return result

確認

テストコード

# test_n_ary.py

import unittest
import n_ary

class TestNAry(unittest.TestCase):
    def test_base2(self):
        self.assertEqual("0", n_ary.base2('0'))
        self.assertEqual("1", n_ary.base2('1'))
        self.assertEqual("101", n_ary.base2('5'))
        self.assertEqual("1010", n_ary.base2('10'))
        self.assertEqual("10000", n_ary.base2('16'))

実行

OK

10進数→N進数への変換

2進数への変換を工夫して、N進数への変換を実装してみます。
62進数まで対応。

# n_ary.py

...

import string
numbers = "0123456789"
alphabets = string.ascii_letters # a-z+A-Zをロード
characters = numbers + alphabets

def base_cvt(value, n=2):
    try:
        tmp = int(value)
    except:
        raise ValueError('Invalid value:', value)

    if n < 2 or n > len(characters):
        raise ValueError('Invalid n:', value)

    result = ''
    tmp = int(value)
    while tmp >= n:
        idx = tmp%n
        result = characters[idx] + result
        tmp = int(tmp / n)
    idx = tmp%n
    result = characters[idx] + result
    return result

確認

テストコード

# test_n_ary.py
...
def test_base(self):
    self.assertEqual('0', n_ary.base_cvt('0'))
    self.assertEqual('1', n_ary.base_cvt('1'))
    self.assertEqual('101', n_ary.base_cvt('5'))
    self.assertEqual('1010', n_ary.base_cvt('10'))
    self.assertEqual('10000', n_ary.base_cvt('16'))

    value = '10000000'

    self.assertEqual('46113200', n_ary.base_cvt(value, 8))
    self.assertEqual('989680', n_ary.base_cvt(value, 16))
    self.assertEqual('5yc1s', n_ary.base_cvt(value, 36))
    self.assertEqual('FXsk', n_ary.base_cvt(value, 62))
    
    # エラー処理
    with self.assertRaises(ValueError):
        # 数字以外だとエラー
        v = n_ary.base_cvt('hoge')
    with self.assertRaises(ValueError):
        # 62進数以上を出そうとするとエラー
        v = n_ary.base_cvt('1000000', 63)
    with self.assertRaises(ValueError):
        # 1以下でもエラー
        v = n_ary.base_cvt('1000000', 1)


    # 一覧出力してみる
    for i in range(2, 63):
        print(n_ary.base_cvt(str(10000000), i))

結果

100110001001011010000000
200211001102101
212021122000
10030000000
554200144
150666343
46113200
20731371
10000000
571016a
3423054
20c187a
148445a
d27e6a
989680
70c715
554c3a
40dhff
32a000
298gfa
1kf33a
1cgkde
16392g
10f000
lmona
im1ba
g7f2o
e40hh
cab3a
apkpk
9h5k0
8e8oa
7gehm
6n89a
5yc1s
5cfma
4u97y
4cmoa
3Aa00
3m3yi
38EDa
2Dxe6
2thcw
2jxca
2axFe
22eHJ
1Gkdg
1zMJv
1u000
1ojym
1j6bA
1e8Qd
19rja
155Ha
10QHo
RUNy
PeBK
MEHv
KhKE
I3rq
FXsk
..
----------------------------------------------------------------------
Ran 2 tests in 0.001s

OK

できているっぽいです。
小数点や負の値を使うのはまたの機会に。

参考