原码·反码·补码
本文可以说是比较水,主要是谈一下原码、反码、补码的东西。原码、反码、补码的定义我们这里不做介绍,可以Google之,查询相关Wiki或百度百科。
我们主要对一个数,如果转换为原码、反码、补码,并且如何由原码、反码、补码转换为原数。下面我们给出几种转换的代码实现,并对代码做一下解释。
需要说明的是,我们并没有对正数用原码负数用补码进行分开讨论,而是对正负数、无符号数都求了原码、反码、补码。关于原码、反码、补码的内部机制,为什么有产生这些,为什么会用它们,用它们有什么好处,我们也不做介绍,如果感兴趣的话可以参考《深入理解计算机系统》。
我们先给出相关的程序,然后对程序进行相关阐述。
#include <iostream> #include <string> using namespace std; // 获取一个数的原码 string TrueForm(int _n) { if (_n == 0) // 如果为0,则返回"00" { return "00"; } // 有符号数求原码,负数和正数一样,只不过最后设置符号位 int n = _n; if (n < 0) { n = -n; } string ret; char ch; while (n != 0) { ch = n % 2 + '0'; ret = ch + ret; n /= 2; } if (_n > 0) { ret = '0' + ret; } else if (_n < 0) { ret = '1' + ret; } return ret; } // 对二进制编码进行逐位翻转 string Negation(const string& _s) { string ret; for (auto i = 0; i != _s.size(); ++i) { ret += '1' - (_s[i] - '0'); } return ret; } // 求反码 string MinusForm(int _n) { string ret = TrueForm(_n); ret = Negation(ret); // 符号位保持0正1负 if (_n >= 0) { ret[0] = '0'; } else { ret[0] = '1'; } return ret; } // 求补码 // 补码的求解方法是先求反码,然后加1,最后根据正负号,置符号位 // 根据反码求补码 string ComplementForm(int _n) { string ret = MinusForm(_n); // 实现二进制加1 int pos = ret.size() - 1; while (pos >= 0) { if (ret[pos] == '0') { ret[pos] = '1'; break; } else { ret[pos] = '0'; } --pos; } if (_n >= 0) { ret[0] = '0'; } else { ret[0] = '1'; } return ret; } // 根据原码直接进行转换,得到补码 // 原码与补码的关系是从右边数,碰到1后,将1左边的其余字符都进行翻转即得补码 // 111000 原码 // 000111 反码 // 001000 补码 // // 101010 原码 // 010101 反码 // 010110 补码 // 符号位还是要坚持0正1负 string ComplementFormByTrue(int _n) { string ret = TrueForm(_n); int pos = ret.size() - 1; while (pos >= 0 && ret[pos] == '0') // 如果为0,则向左继续查找 { --pos; } // 退出循环后,ret[pos]为1,该位需要保留 --pos; // 对剩下的pos到0进行逐位翻转 while (pos >= 0) { ret[pos] = '1' - (ret[pos] - '0'); --pos; } // 坚持符号位 if (_n >= 0) { ret[0] = '0'; } else { ret[0] = '1'; } return ret; } // 以上是对有符号数的转换,有符号的数有一个符号位 // 下面是关于无符号的,无符号位 string TrueFormUnsigned(unsigned int _n) { if (_n == 0) { return "0"; } string ret; char ch; unsigned int n = _n; while (n != 0) { ch = '0' + n % 2; ret = ch + ret; n /= 2; } return ret; } string MinusFormUnsigned(unsigned int _n) { string ret = TrueFormUnsigned(_n); ret = Negation(ret); // 不涉及符号位 return ret; } // 通过反码求补码 string ComplementFormUnsigned(unsigned int _n) { string ret = MinusFormUnsigned(_n); // 实现二进制加1 int pos = ret.size() - 1; while (pos >= 0) { if (ret[pos] == '0') { ret[pos] = '1'; break; } else { ret[pos] = '0'; } --pos; } // 不涉及符号位 return ret; } // 通过原码求补码 string ComplementFormUnsignedByTrue(unsigned int _n) { string ret = TrueFormUnsigned(_n); // 从右到左求出为1的pos int pos = ret.size() - 1; while (pos >= 0 && ret[pos] == '0') { --pos; } // 退出循环时,pos指向最右边的1,本位的1保留 --pos; // 逐位翻转 while (pos >= 0) { ret[pos] = '1' - (ret[pos] - '0'); --pos; } // 不涉及符号位 return ret; } // 以上是对数到原码、反码、补码的转换 // 下面讨论一下原码、反码、补码到数的转换 int TrueFormToInt(const string& tf) { int ret = 0; for (int i = 1; i != tf.size(); ++i) { ret = ret * 2 + tf[i] - '0'; } if (tf[0] == '1') { ret = -ret; } return ret; } // 将其转换为原码,再计算 int MinusFormToInt(const string& mf) { int ret = 0; string tmp = Negation(mf); if (mf[0] == '0') { tmp[0] = '0'; } else { tmp[0] = '1'; } ret = TrueFormToInt(tmp); return ret; } // 将其转换为原码,再计算 // 也可将其转换为反码再计算 // 补码转换为原码有两种方式: // 1.为将其减1,然后取反,即原码求补码的逆过程 // 2.为将其翻转,然后加1 // 111000 原码 取反 // 000111 反码 减1 // 001000 补码 // // 110111 取反 // 111000 加1 // // // 101010 原码 取反 // 010101 反码 减1 // 010110 补码 // // 101001 取反 // 101010 加1 // 第1种方式很好理解,即是逆过程 // 第2种方式是因为原码最右的1之前的数都都翻转,其后(包括该1)都保持不变 // 先翻转,则导致前面的翻转为原码,后面的与原码相反 // 然后再加1,导致后面的都翻转了,进而和原码一致 // 另外也可以只通过减1的方式得到反码,再由反码求得值,本质上与第1种方法一样 // int ComplementFormToInt(const string& cf) { string tmp = cf; // 对tmp进行减1操作 int pos = tmp.size() - 1; while (pos >= 0) { if (tmp[pos] == '1') { tmp[pos] = '0'; break; } else { tmp[pos] = 1; } --pos; } if (cf[0] == '0') { tmp[0] = '0'; } else { tmp[0] = '1'; } return MinusFormToInt(tmp); } // 以上是将原码、反码、补码转换为有符号数 // 下面我们将其转换为无符号数 unsigned int TrueFormToUnsignedInt(const string& tf) { unsigned int ret = 0; for (int i = 0; i != tf.size(); ++i) { ret = ret * 2 + tf[i] - '0'; } return ret; } // 反码求无符号数 // 通过原码求 unsigned int MinusFormToUnsignedInt(const string& mf) { string tmp = Negation(mf); return TrueFormToUnsignedInt(tmp); } // 补码求无符号数 // 通过反码求 unsigned int ComplementFormToUnsignedInt(const string& cf) { string tmp = cf; // 对tmp减1操作 int pos = tmp.size() - 1; while (pos >= 0) { if (tmp[pos] == '1') { tmp[pos] = '0'; break; } else { tmp[pos] = '0'; } --pos; } return MinusFormToUnsignedInt(tmp); //// 另一种实现tmp减1的操作 //while (pos >= 0 && tmp[pos] == '0') //{ // --pos; //} //if (pos >= 0) //{ // tmp[pos] = '0'; //} } int main() { int n = 0; string s; while (cin >> n) { cout << n << endl; cout << TrueForm(n) << endl; cout << MinusForm(n) << endl; cout << ComplementForm(n) << endl; cout << ComplementFormByTrue(n) << endl; cout << endl; unsigned int o = (unsigned int)n; cout << TrueFormUnsigned(o) << endl; cout << MinusFormUnsigned(o) << endl; cout << ComplementFormUnsigned(o) << endl; cout << ComplementFormUnsignedByTrue(o) << endl; cout << endl; cin >> s; cout << TrueFormToInt(s) << endl; cout << MinusFormToInt(s) << endl; cout << ComplementFormToInt(s) << endl; cout << endl; cout << TrueFormToUnsignedInt(s) << endl; cout << MinusFormToUnsignedInt(s) << endl; cout << ComplementFormToUnsignedInt(s) << endl; cout << endl; } return 0; }
代码中有一些注释解释。
我们的程序主要实现了如下几个函数:
函数 |
作用 |
TrueForm |
将有符号数转换为原码 |
MinusForm |
将有符号数转换为反码 |
ComplementForm |
将有符号数转换为补码,通过反码的方式 |
ComplementFormByTrue |
将有符号数转换为补码,通过原码的方式 |
TrueFormUnsigned |
将无符号数转换为原码 |
MinusFormUnsigned |
将无符号数转换为反码 |
ComplementFormUnsigned |
将无符号数转换为补码,通过反码的方式 |
ComplementFormUnsignedByTrue |
将无符号数转换为补码,通过原码的方式 |
TrueFormToInt |
将原码转换为有符号数 |
MinusFormToInt |
将反码转换为有符号数 |
ComplementFormToInt |
将补码转换为有符号数,通过反码的方式 |
TrueFormToUnsignedInt |
将原码转换为无符号数 |
MinusFormToUnsignedInt |
将反码转换为无符号数 |
ComplementFormToUnsignedInt |
将补码转换为无符号数,通过反码的方式 |
Negation |
逐位取反 |
求一个数的二进制形式有很多种方法,我们用的就是最为经典的除二取余法。除此之外还可以调用库函数、运用位运算(本质上还是除二取余法)等等。
有符号数中,最左边的第1位为符号位,0正1负。无符号数无符号位。对于有符号的0,我们直接将其返回“00”,第一位是符号位,第二位为值,虽然为0,还是要占一位。对于无符号数的0,我们之间返回“0”。
反码的计算即使将原码各位取反,保留符号位。
补码的计算有两种方式,一是通过原码的取反,加1,并保留符号位。另一种方法是根据原码来求,对从最右边1的左边所有位取反。
对二进制数进行加1或减1操作数,只需判断当前为是否满足条件即可(这里有两种实现方式,A[pos]和pos)。逻辑与大数加减算法类似。
逐位翻转的操作是:a = ‘1’ – (a – ‘0’),如果a = ‘0’,则 a = ‘1’ – (‘0’ – ‘0’) = ‘1’;如果a = ‘1’,则a = ‘1’ – (‘1’ – ‘0’) = ‘1’ – 1 = ‘0’。
求由补码到原码的方法有两种,第一种是减1取反,第二种是取反加1。具体可以参考代码中注释247~272行。
总结
以上我们对原码、反码、补码与有符号数、无符号数之间的相互转换进行了讨论和实现。