2015/10/05 このエントリーをはてなブックマークに追加 はてなブックマーク - Javaの10進数からN進数変換はどうやったらスマートか

Javaの10進数からN進数変換はどうやったらスマートか








最近、進数変換が実装として必要な場面がありました。
10進数からN進数への変換。


みんなどうしてるの?

ということで解決策を考えてみます。







今回は10進数から32進数と62進数の変換です。

10進数は日常的に使われていますね。
0123456789で表現されます。9の次は位が上がって10と表現します



32進数は

0123456789
ABCDEFGHIJ
KLMNOPQRST
UV

です。Base32とはことなるようです。
0~Vまでの32進数表記はbase32hexと呼びます。RFC 4648で規格として決まっているようです。

62進数は
0123456789
ABCDEFGHIJKLMNOPQRSTUVWXYZ
abcdedghijklmnopqrstuvwxyz

です。






10進数の値をひたすら基数で割った余りと商に分解し、余りを連結してリバースすれば0から基数の間の値になります。


それを進数のとりうる値にマッピングすれば、進数変換が出来るのですが。


こんな感じです。



   /**
     * 10進数をn進数に変換する
     * 
     * @param radix 基数
     * @param value 10進数のint値
     * @return 進数変換した文字列
     */
    public String convertTenRadixTo(int radix, int value) {

        List<Integer> list = new ArrayList<>();
        if (radix == 0 || value == 0) {
            return "0";
        }
        
        // ひたすら基数割って余りをリストにadd(0〜Nの値を持つリストになる)
        while (value > 0) {
            // 基数での余り
            list.add(value % radix);
            // 元の値を基数で割った商で上書き
            value = (value / radix);
        }
        StringBuilder builder = new StringBuilder();
        
        // 1〜Nまでの値を進数の値にマッピング
        for (Integer number : list) {
            builder.append(RADIX_MAP.get(String.valueOf(number)));
        }
        
        // 順序が逆になっているのでリバースしてtoString
        return builder.reverse().toString();
    }

    /**
     * 手抜きのMap
     */
    private static final Map<String, String> RADIX_MAP = new HashMap<String, String>() {
        {
            put("0", "0"); put("1", "1"); put("2", "2"); put("3", "3");
            put("4", "4"); put("5", "5"); put("6", "6"); put("7", "7");
            put("8", "8"); put("9", "9"); put("10", "a"); put("11", "b");
            put("12", "c"); put("13", "d"); put("14", "e"); put("15", "f");
            put("16", "g"); put("17", "h"); put("18", "i"); put("19", "j");
            put("20", "k"); put("21", "l"); put("22", "m"); put("23", "n");
            put("24", "o"); put("25", "p"); put("26", "q"); put("27", "r");
            put("28", "s"); put("29", "t"); put("30", "u"); put("31", "v");
            put("32", "w"); put("33", "x"); put("34", "y"); put("35", "z");
        }
    };


…………。Javaでオレオレ実装するのはなるべく避けたい。








そんなあなたにおすすめなのがjava.lang.Integerです。モダンなJavaのクラスですね。


Integer#parseInt(String s, int radix)
Integer#toString(int i, int radix) って感じで進数変換が出来ちゃいます!


その他、2進数への8進数や16進数への変換メソッドが用意されています。
Integer#toBinaryString(int i)・・・2進数
Integer#toOctalString(int i)・・・8進数
Integer#toHexString(int i)・・・16進数


Integer#decodeメソッドでも8進数や16進数への変換が出来ます。



この方の記事が使い方として分かりやすいですね。
n進数の表現 - chihiro on Qiita


でもIntegerの精度を越える範囲の値を扱う場合はどうするんだ!となります。


そういうときはjava.math.BigIntegerです!モダンなJavaのクラスですね。


BigIntegerインスタンスを生成してtoString(int radix)メソッドを使えば進数変換できちゃいます!





では、BigIntegerはどのぐらいまでの値を持てるのでしょうか。


調べてみるとStack Overflowにたどりつきました。
Is there an upper bound to BigInteger? [duplicate]

どうやらBigIntegerの内部ではintの配列で値を持っているらしく、それが持てる値の範囲になる、と。



つまり(2^32)^Integer.MAX_VALUEらしいです。


ほぼこれでイケるじゃん!やったね!





ところがBigIntegerにも限界があるっぽいのです。話が一転しちゃいました。

BigIntegerのtoString(int radix)メソッドは36進数までしか対応できない!なんで?


これはCharcterのMIN_RADIX ~MAX_RADIXの範囲を越えてしまうと変換が出来ないかららしいです。
BigIntegerのjavadocに書いてあります。


これはIntegerに関しても同様のようです。






ということで、結局62進数はオレオレ実装するしかない。ということに…(たぶん)


調べてみるといろんな人がオレオレ実装してるのが出てきます。






こぼれ話ですがBase64に関してはJava SE 8からBase64クラスが導入されました。


Base64.getEncoder().encodeToString(byte[] value)とかが出来ます。



komiya-atsushi/Base64Demo.java


あと、BigDecimalのとりうる値は理論上 2793926648桁らしいです?
BigDecimalといえば、きどさんという方のブログによく行く着きます。
java.math.BigDecimalの最大桁数(理論値)- きどたかのブログ





ということで、まとめです。

・36進数まではBigIntegerでだいたいイケる
・37進数以降オレオレ実装するしかなさそう
(なんか良いライブラリ無いかな?)


結論がオレオレ実装しましょう、ってやだなぁ〜。





なるほど〜。



0 件のコメント:

コメントを投稿