进制之间的转换
一、谈谈几个库函数
函数 |
原型 |
功能 |
参考 |
atoi |
int atoi(const char* nptr); |
把字符串换化成整型数 |
|
itoa |
char* itoa(int value, char* string, int radix); |
将整型数转换为radix进制数的字符串形式 |
|
atol |
long atol(const char* nptr); |
把字符串转换成长整型数 |
|
atof |
double atof(const char* nptr); |
把字符串转换成浮点数 |
|
strtod |
double strtod(const char* nptr, char** endptr); |
把字符串转换成浮点数 |
|
strtol |
long int strtol(const char* nptr, char** endptr, int base); |
将nptr字符串根据base转换成长整型数 |
|
strtoul |
unsigned long strtoul(const char* nptr, char** endptr, int base); |
将字符串转换成无符号长整型数 |
以上函数,atoi是将字符串转换为整形数,itoa是根据radix的值将整形数按照radix转换为字符串,atol是将字符串转换为long型数,atof是将字符串转换为double型数,strtod是将字符串转换为浮点型,strtol是将字符串根据base转换为long型数,strtoul是将字符串根据base转换为unsigned long型数。
itoa是从int到字符串,radix是针对字符串来说的;
strtol、strtoul是从字符串到long、unsigned long的,base同样是相对于字符串来说的。
二、数值的表示形式
在程序中具体的数值类型有int、long int、unsigned long int、float、double等等类型,对应地其表达形式可以有字符串来表示,上面列举的函数其实质就是来实现数值类型和字符串之间的相互转换。
我们这里针对数值的表示形式统一采用字符串的形式,程序内部的实现当然还是用具体的数值类型。
三、实现一个最基本的转换——十进制到二进制的转换
十进制到二进制的转换,我们的入参都是采用字符串的,所以十进制数是用字符串的形式表示,同样出参我们同样是用字符串的形式来表达。之后我们每个转换函数其入参和出参都是字符串类型。
如果利用上面的函数itoa来求解该问题,那么入参须是int型的数,具体的调用形式如下:
itoa(value, bin_arry, 2);
十进制转换为二进制的方法就是除二取余法,比如一十进制数20,我们采用除二取余法计算的过程为:
步骤 |
被除数 |
除数 |
商 |
余数 |
1 |
20 |
2 |
10 |
0 |
2 |
10 |
2 |
5 |
0 |
3 |
5 |
2 |
2 |
1 |
4 |
2 |
2 |
1 |
0 |
5 |
1 |
2 |
0 |
1 |
6 |
0 |
2 |
0 |
0 |
7 |
0 |
2 |
0 |
0 |
… |
… |
… |
… |
… |
计算过程终止的条件即是:被除数==0,最终的二进制字符串为余数的倒置,即:
10100
对于字符串的十进制数,我们可以采用atoi函数求解,这里我们实现一个自己的版本。
具体的程序如下:
// 十进制转二进制——除二取余法 long DecimalFromStringToLong(const string& decimal) { long ret = 0; for (auto i = 0; i != decimal.size(); ++i) { ret = ret * 10 + decimal[i] - '0'; } return ret; } string DecimalToBinary(const string& decimal, string& binary) { binary.clear(); long dec = DecimalFromStringToLong(decimal); while (dec != 0) { binary += dec % 2 + '0'; dec /= 2; } reverse(binary.begin(), binary.end()); return binary; }
这里我们还给出一种递归实现:
// 除二取余法求解十进制转二进制——递归实现 long DecimalFromStringToLong(const string& decimal) { long ret = 0; for (auto i = 0; i != decimal.size(); ++i) { ret = ret * 10 + decimal[i] - '0'; } return ret; } string DecimalFromLongToString(long dec) { string ret; while (dec != 0) { char ch = dec % 10 + '0'; ret = ch + ret; dec /= 10; } return ret; } string _DecimalToBinaryRecursion(const string& decimal, string& binary) { long dec = DecimalFromStringToLong(decimal); if (dec != 0) { char ch = dec % 2 + '0'; return _DecimalToBinaryRecursion(DecimalFromLongToString(dec / 2), binary) + ch; } else { return binary; } } string DecimalToBinaryRecursion(const string& decimal, string& binary) { binary.clear(); return _DecimalToBinaryRecursion(decimal, binary); }
四、M进制到N进制的转换
前面从十进制到二进制转换,我们利用了除二取余法,下面我们尝试从M进制到N进制进行转换。其原理是,先计算待转换M进制数的值,然后根据除N取余法得到转换后的N进制数。
这种方法实质上是利用十进制数在中间做中介,先将M进制数转换为10进制数,然后由10进制数根据除N取余法得到N进制数。
// M进制到N进制的转换 // M进制数转换为10进制的long long MSystemFromStringToLong(const string& decimal, int m, map<char, long>& msys) { long ret = 0; for (auto i = 0; i != decimal.size(); ++i) { ret = ret * m + msys[decimal[i]]; } return ret; } // 10进制long转换为M进制数 string MSystemFromLongToString(long dec, int m, map<long, char>& msys) { string ret; while (dec != 0) { char ch = msys[dec % m]; ret = ch + ret; dec /= m; } return ret; } // M进制到N进制之间的转换 string MSystemToNSystem(const string& mnumber, string& nnumber, int m, int n, map<char, long>& msys, map<long, char>& nsys) { nnumber.clear(); long tmp = MSystemFromStringToLong(mnumber, m, msys); nnumber = MSystemFromLongToString(tmp, n, nsys); return nnumber; }
我们对M进制、N进制定义了其各自字符对应于10进制的值,根据预定义的字符-值系统得到M进制的10进制数,然后对10进制数采用除N取余法得到N进制数。
五、一些特殊进制之间的转换
以上M进制转换为N进制,我们是在中间处理过程借助了10进制作为中介。但是一些特殊进制数可以不用借助10进制数来做中介,而是直接进行转换。例如,二进制与八进制之间的转换,3个二进制位相当于1个八进制位,1个八进制位相当于3个二进制位,其关系如下:
二进制 |
八进制 |
000 |
0 |
001 |
1 |
010 |
2 |
011 |
3 |
100 |
4 |
101 |
5 |
110 |
6 |
111 |
7 |
同样,二进制数与十六进制数之间的转换也是如此,一个十六进制位对应于4个二进制位。但有一定需要注意,二进制与八进制、十六进制之间的转换需要从低位到高位进行,不能从高位到低位。
另外,八进制和十六进制之间的转换不能直接按位组装,可以借助10进制作为中介,抑或借助于二进制数作为中介进行转换。
六、阿拉伯数字技术法的讨论
在这里不管我们讨论的哪种进制,其计数法都是采用的阿拉伯数字计数法。注意,这里我们所说的是阿拉伯数字计数法,而非阿拉伯数字,阿拉伯数字是十进制数,我们这里的M进制基数形式是借助于阿拉伯数字计数法,但是进制绝对不仅仅是十。
除了阿拉伯数字计数法外,还有很多其他的,比如罗马数字计数法、英文计数、汉语基数、大写汉字级数等等。关于不同计数法之间的转换不是我们这里所讨论的,我们这里讨论的是基于阿拉伯数字计数法的不同进制之间的相互转换。
七、考虑小数的情况
对于整数的转换,我们采用的是除M取余法。对于小数的进制转换我们有如下方法:
待转换M进制小数:
ABC.EFG
我们首先将其分割为两部分:ABC和EFG
针对ABC,我们还是按照之前的整数转换方法进行整数的转换,转换为N进制整数。关于整数与小数之间为什么能分割是因为小数必定小于1,所以转换为N进制的结果小数部分依然对应于小数部分,整数部分依然对应于整数部分。
然后对EFG进行小数部分之间的转换,我们首先将EFG转换为10进制的小数,然后对10进制的小数转换为N进制的小数,最后将N进制的整数和小数进行拼接,即得转换后的N进制数。
转换ABC.EFG |
|
分割整数部分和小数部分 |
|
整数部分 |
小数部分 |
ABC |
EFG |
各位依次乘以M,得到10进制整数 |
各位依次除以M,得到10进制小数 |
对10进制小数,依次除以N,得到N进制整数 |
对10进制小数,依次乘以N,得到N进制小数 |
将N进制的整数和小数进行拼接,即得转换后的N进制数 |
从上表我们可以看出对于整数部分的转换,我们是先乘后除,这里的除即是除N取余法。对于小数部分的转换,我们是先除后乘,这里的乘是指乘N取整法。
具体转换程序如下:
// 包含小数的不同进制之间的转换 // M进制数转换为10进制的long long MSystemFromStringToLong(const string& decimal, int m, map<char, long>& msys) { long ret = 0; for (auto i = 0; i != decimal.size(); ++i) { ret = ret * m + msys[decimal[i]]; } return ret; } // 10进制long转换为M进制数 string MSystemFromLongToString(long dec, int m, map<long, char>& msys) { string ret; while (dec != 0) { char ch = msys[dec % m]; ret = ch + ret; dec /= m; } return ret; } // M进制到N进制的转换 string MSystemToNSystem(const string& mnumber, string& nnumber, int m, int n, map<char, long>& msys, map<long, char>& nsys) { nnumber.clear(); long tmp = MSystemFromStringToLong(mnumber, m, msys); nnumber = MSystemFromLongToString(tmp, n, nsys); return nnumber; } // M进制小数数转换为10进制的double小数 double MSystemFromStringToLongDot(const string& decimal, int m, map<char, long>& msys) { double ret = 0; for (int i = decimal.size() - 1; i >= 0; --i) { ret = ret / m + 1.0 * msys[decimal[i]] / m; } return ret; } // 10进制double小数转换为M进制小数 string MSystemFromLongToStringDot(double dec, int m, map<long, char>& msys) { string ret; int counter = 0; while (dec != 0 && counter++ < 10) // 最多支持10个小数位,不会无限循环下去 { char ch = msys[dec * m]; ret = ret + ch; dec = dec * m - (long)(dec * m); } return ret; } // 包含小数部分的M进制到N进制的转换 string MSystemToNSystemDot(const string& mnumber, string& nnumber, int m, int n, map<char, long>& msys, map<long, char>& nsys) { nnumber.clear(); double tmp = MSystemFromStringToLongDot(mnumber, m, msys); nnumber = MSystemFromLongToStringDot(tmp, n, nsys); return nnumber; } string MSystemToNSystemIntAndFrac(const string& mnumber, string& nnumber, int m, int n, map<char, long>& msys, map<long, char>& nsys) { nnumber.clear(); string m_int, m_frac; string n_int, n_frac; auto pos = mnumber.find('.'); m_int = mnumber.substr(0, pos - 0); m_frac = mnumber.substr(pos + 1); MSystemToNSystem(m_int, n_int, m, n, msys, nsys); MSystemToNSystemDot(m_frac, n_frac, m, n, msys, nsys); nnumber = (n_int.empty() ? string("0") : n_int) + (n_frac.empty() ? string("") : (string(".") + n_frac)); return nnumber; }
由于某些进制表现能力的欠缺,在做小数部分转换时可能会导致无限转换,所以我们规定了小数位数最大为10。比如十进制0.3转换为二进制小数位:0.0100110011,其实后面还有很多位,由二进制小数0.0100110011转换为十进制小数位:0.2998046875。
八、关于进制之间转换的递归实现
在前面十进制转换为二进制部分中,我们谈论了非递归实现和递归实现,同样地M进制到N进制的转换同样可以采用递归实现,这里我们不再做赘述,具体做法可以参考十进制转二进制的递归算法。有关递归的思想请参见《递归的讨论》。
九、总结
本文初衷主要是想讨论不同进制之间的任意相互转换。我们首先讨论了C/C++库函数对这方面的支持,然后谈论了数值的表现形式,并用非递归和递归方法实现了十进制数转二进制数,进而实现了M进制到N进制的转换,之后我们又谈论了一些关于数值的计数方法,明确我们本文所涉及的是阿拉伯数字计数法下的不同进制之间的相互转换,而非对不同计数法之间转换的讨论,进而对含有小数部分的不同进制之间的转换进行了讨论,针对含有小数部分的做法,我们是先对其进行分割,对分割出的整数部分依然采用除N取余法进行求解,对于小数部分的转换,我们采用乘N取整法进行转换。最后我们对递归实现进制间转换就行了进一步的说明。
附:进制转换程序及测试代码(详见注释)
#include <iostream> #include <string> #include <map> #include <algorithm> using namespace std; long DecimalFromStringToLong(const string& decimal) { long ret = 0; for (auto i = 0; i != decimal.size(); ++i) { ret = ret * 10 + decimal[i] - '0'; } return ret; } string DecimalFromLongToString(long dec) { string ret; while (dec != 0) { char ch = dec % 10 + '0'; ret = ch + ret; dec /= 10; } return ret; } string DecimalToBinary(const string& decimal, string& binary) { binary.clear(); long dec = DecimalFromStringToLong(decimal); while (dec != 0) { char ch = dec % 2 + '0'; binary = ch + binary; dec /= 2; } return binary; } string _DecimalToBinaryRecursion(const string& decimal, string& binary) { long dec = DecimalFromStringToLong(decimal); if (dec != 0) { char ch = dec % 2 + '0'; return _DecimalToBinaryRecursion(DecimalFromLongToString(dec / 2), binary) + ch; } else { return binary; } } string DecimalToBinaryRecursion(const string& decimal, string& binary) { binary.clear(); return _DecimalToBinaryRecursion(decimal, binary); } // M进制数转换为10进制的long long MSystemFromStringToLong(const string& decimal, int m, map<char, long>& msys) { long ret = 0; for (auto i = 0; i != decimal.size(); ++i) { ret = ret * m + msys[decimal[i]]; } return ret; } // 10进制long转换为M进制数 string MSystemFromLongToString(long dec, int m, map<long, char>& msys) { string ret; while (dec != 0) { char ch = msys[dec % m]; ret = ch + ret; dec /= m; } return ret; } // M进制到N进制的转换 string MSystemToNSystem(const string& mnumber, string& nnumber, int m, int n, map<char, long>& msys, map<long, char>& nsys) { nnumber.clear(); long tmp = MSystemFromStringToLong(mnumber, m, msys); nnumber = MSystemFromLongToString(tmp, n, nsys); return nnumber; } // M进制小数数转换为10进制的double小数 double MSystemFromStringToLongDot(const string& decimal, int m, map<char, long>& msys) { double ret = 0; for (int i = decimal.size() - 1; i >= 0; --i) { ret = ret / m + 1.0 * msys[decimal[i]] / m; } return ret; } // 10进制double小数转换为M进制小数 string MSystemFromLongToStringDot(double dec, int m, map<long, char>& msys) { string ret; int counter = 0; while (dec != 0 && counter++ < 10) // 最多支持10个小数位,不会无限循环下去 { char ch = msys[dec * m]; ret = ret + ch; dec = dec * m - (long)(dec * m); } return ret; } // 包含小数部分的M进制到N进制的转换 string MSystemToNSystemDot(const string& mnumber, string& nnumber, int m, int n, map<char, long>& msys, map<long, char>& nsys) { nnumber.clear(); double tmp = MSystemFromStringToLongDot(mnumber, m, msys); nnumber = MSystemFromLongToStringDot(tmp, n, nsys); return nnumber; } string MSystemToNSystemIntAndFrac(const string& mnumber, string& nnumber, int m, int n, map<char, long>& msys, map<long, char>& nsys) { nnumber.clear(); string m_int, m_frac; string n_int, n_frac; auto pos = mnumber.find('.'); m_int = mnumber.substr(0, pos - 0); m_frac = mnumber.substr(pos + 1); MSystemToNSystem(m_int, n_int, m, n, msys, nsys); MSystemToNSystemDot(m_frac, n_frac, m, n, msys, nsys); nnumber = (n_int.empty() ? string("0") : n_int) + (n_frac.empty() ? string("") : (string(".") + n_frac)); return nnumber; } int main() { string binary; cout << DecimalToBinary("20", binary) << endl; cout << DecimalToBinaryRecursion("20", binary) << endl; map<char, long> msys; map<long, char> msys_2; for (int i = 0; i < 10; ++i) { msys[i + '0'] = i; msys_2[i] = i + '0'; } map<long, char> nsys; map<char, long> nsys_2; for (char ch = 'A'; ch < 'Z' + 1; ++ch) { nsys[ch-'A'] = ch; nsys_2[ch] = ch - 'A'; } string nnumber; cout << MSystemToNSystem("28", nnumber, 10, 26, msys, nsys) << endl; cout << MSystemToNSystem("BC", nnumber, 26, 10, nsys_2, msys_2) << endl; cout << MSystemToNSystem("288", nnumber, 10, 10, msys, msys_2) << endl; cout << MSystemToNSystem("BCZ", nnumber, 26, 26, nsys_2, nsys) << endl; map<long, char> bsys; map<char, long> bsys_2; for (char ch = '0'; ch < '2'; ++ch) { bsys[ch - '0'] = ch; bsys_2[ch] = ch - '0'; } cout << MSystemToNSystemIntAndFrac("1.5", nnumber, 10, 2, msys, bsys) << endl; cout << MSystemToNSystemIntAndFrac("1.1", nnumber, 2, 10, bsys_2, msys_2) << endl; cout << MSystemToNSystemIntAndFrac("0.3", nnumber, 10, 2, msys, bsys) << endl; cout << MSystemToNSystemIntAndFrac("0.0100110011", nnumber, 2, 10, bsys_2, msys_2) << endl; cout << MSystemToNSystemIntAndFrac("20.55", nnumber, 10, 26, msys, nsys) << endl; cout << MSystemToNSystemIntAndFrac("U.OHUUUUUUUU", nnumber, 26, 10, nsys_2, msys_2) << endl; return 0; }