Wenzi

各种 2 到 62 任意进制之间的转换

蚊子前端博客
发布于 2022/04/28 21:00
如何实现一个任意进制转换的方法

我们在平时生活中通常会遇到 10 进制转其他进制,或其他进制转为 10 进制,那我们就可以通过 10 进制进行中转,实现各种任意进制的转换。

大部分编程语言都仅限在 2-36 进制内的转换,这里我们拓展到 62 进制,即小写字母z的下一位用大写字母A表示,直到大写字母Z

我们先来熟悉下 10 进制之间的转换,然后进行统一。

1. 其他进制转为 10 进制 #

其他进制转为 10 进制时,将当前的基数乘以当前的位权,然后全部相加即可,如一个 8 进制的数字 123:

123 = 1 * 8^2 + 2 * 8^1 + 3 * 8^0;
    = ((1 * 8) + 2) * 8 + 3;

通过这个简单的推导,我们在用代码实现时,可以从左到右,先用当前数字乘以进制,然后再参与下一位的运算,直到结束。

进制超过 10 时,会出现字母,这里我们需要把字母转为数字再进行计算。

字母转为数字:

/**
  * 将letter转为纯数字
  * @param {char} letter
  * @return {int}
  */
int transformCharToNum(char letter)
{
    if (letter >= '0' && letter <= '9')
    {
        return letter - '0';
    }
    if (letter >= 'a' && letter <= 'z')
    {
        return letter - 'a' + 10;
    }
    if (letter >= 'A' && letter <= 'Z')
    {
        return letter - 'A' + 36;
    }
    return 0;
}

其他进制转为 10 进制的具体实现:

/**
 * 将进制为base的字符串数字num,转为10进制的数字
 * @param {string} num 要转换的数字
 * @param {int} base 该数字的进制
 */
int covertOtherTo10(string num, int base)
{
    int p = 0, number10 = 0;
    while (p < num.length())
    {
        number10 *= base;
        number10 += transformCharToNum(num[p]); // 将字母转为纯数字,然后参与运算
        p++;
    }
    return number10;
}

使用方式:

covertOtherTo10("1101", 2); // 将2进制的数字1101转为10进制,结果为13
covertOtherTo10("2e", 16); // 将16进制的数字2e转为10进制,结果为46

2. 10 进制转为其他进制 #

10 进制的数字转为其他进制的数字,我们用到的短除法,即对某进制取余,获取到的余数即为该位置的基数。

转换后的进制可能比较大,如在 16 进制中,a 表示 10,b 表示 11 等,这里我们要有一个数字到字母的对照表。

我们用 C++ 来实现下:

/**
  * 将数字转为进制里的字母
  * @param {int} num
  * @return {char}
  */
int transformNumToChar(int num)
{
    string str = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    return str[num];
}

10 进制转为其他进制的具体实现:

string covert10ToOther(int num, int to) {
  int cur;
  string result;

  while (num) {
      cur = num % to;
      result.push_back(transformNumToChar(cur)); // 将数组转为字母,如10->a
      num /= to;
  }
  reverse(result.begin(), result.end());
  return result;
}

使用方式:

covert10ToOther(13, 2); // 将10进制里的13转为2进制,结果为1101
covert10ToOther(7, 2); // 111
covert10ToOther(13, 5); // 23

3. 任意进制转任意进制 #

任意进制之间的转换,我们只需要把上面的两种方式组合在一起就行了。这里中间需要 10 进制中转一下。

我们将进制转换扩展到了 62 进制,里面可能会包含英文字符,因此我们的输入和输出都定义成了 string 类型。若您需要的是纯数字格式的,还请自行转换。

class Solution
{
public:
    /**
     * 将num从base进制转为to指定的进制
     * @param {string} num 要转换的数字字符串
     * @param {int} base num的进制
     * @param {int} to 转换后的进制
     * @return {string}
     */
    string covert(string num, int base, int to)
    {
        // 当base和to相等 或 base和to超出转换范围,则原样返回
        if (base == to || !this->checkRadixLegal(base) || !this->checkRadixLegal(to))
        {
            return num;
        }

        // 先转成10进制
        int p = 0, number10 = 0;
        while (p < num.length())
        {
            number10 *= base;
            number10 += this->transformCharToNum(num[p]);
            p++;
        }

        // 若要转换的正好是进制,则直接返回
        if (to == 10) {
            return to_string(number10);
        }

        int cur;
        string result;
        while (number10) {
            cur = number10 % to;
            result.push_back(this->transformNumToChar(cur));
            number10 /= to;
        }
        reverse(result.begin(), result.end());
        return result;
    }

private:
    bool checkRadixLegal(int radix)
    {
        return radix >= 2 && radix <= 62;
    }

    /**
     * 将letter转为纯数字
     * @param {char} letter
     * @return {int}
     */
    int transformCharToNum(char letter)
    {
        if (letter >= '0' && letter <= '9')
        {
            return letter - '0';
        }
        if (letter >= 'a' && letter <= 'z')
        {
            return letter - 'a' + 10;
        }
        if (letter >= 'A' && letter <= 'Z')
        {
            return letter - 'A' + 36;
        }
        return 0;
    }

    /**
     * 将数字转为进制里的字母
     * @param {int} num
     * @return {char}
     */
    int transformNumToChar(int num)
    {
        string str = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

        return str[num];
    }
};

我们将进制扩展到了 62 进制内的任意进制,因此输入和输出均为 string 类型。

使用方式:

auto aa = new Solution();
cout << aa->covert("1101", 2, 10); // "1101"为2进制数字,将其转为10进制,结果为13
cout << aa->covert("13", 10, 5) << endl;
cout << aa->covert("7", 10, 2) << endl;
cout << aa->covert("1101", 2, 16) << endl;
cout << aa->covert("456", 7, 2) << endl;
cout << aa->covert("2e", 16, 10) << endl;
cout << aa->covert("7aZ", 62, 10) << endl;

/*
1101 from 2  to 10 is 13
13   from 10 to 5  is 23
7    from 10 to 2  is 111
1101 from 2  to 16 is d
456  from 7  to 2  is 11101101
2e   from 16 to 10 is 46
7aZ  from 62 to 10 is 27589
*/

我们就可以在 62 进制内实现任意进制的任意转换了。

4. JavaScript 中任意进制的转换 #

在 JavaScript 中,有两个系统方法 parseInt 和 toString,综合运用这两个方法,可以实现 36进制内的任意进制的转换。

  • parseInt(string, radix): 将任意进制 radix(36 进制内)转为 10 进制的数字,radix 表示 string 本身是多少进制的;
  • num.toString(radix): 将 10 进制的数字转为任意进制 radix 的字符串,radix 表示要转换成多少进制的;

我们这里来封装一下:

const covert = (num: string, base: number, to: number) => {
  return parseInt(num, base).toString(to);
};

使用方式与上面的 C++代码实现的方式一样:

covert('1101', 2, 10); // 13
covert('13', 10, 5); // 23
covert('2e', 16, 10); // 46

若需要扩展到更大的进制范围,就得需要自行实现了,具体实现方式可以参考上面 C++的代码。

标签:algorithm
阅读(1212)
Simple Empty
No data