标签:... 符号 二进制 sum B2T 补码 再谈 cdot
写在前面:此文章不是介绍补码的基础知识,在您向下阅读之前,确保已经对数据的二进制表示有一定的了解,否则请勿浪费时间。
在初学计算机组成原理 (ICS) 的时候,入门的第一课便是数据的编码表示,其中一个重要的部分就是补码 (Complement) 。
在学习补码时,有个结论是经常提到的,而且要求我们记住:
正数的补码是其二进制表示本身,负数的补码是对应正数补码的按位取反再加一。
这个结论等价于:对于某个整型 \(k\) ,\(k\) 的相反数:-k = ~k + 1
。
在大二学习 ICS 这门课的时候,我经常使用这个结论去写出负数的补码(先写正数的补码,然后按位取反再加一)。
但同时我有一个「坏习惯」,当我想把某个负数的补码转换为 10 进制表示时,我总是先把补码按位取反再加一,然后求出正数,即可求得负数。
在学习 CMU 的 15213-CSAPP
的 Course 时,从另外一种角度来理解补码似乎更符合我的思维习惯。
设 \(X=x_{w-1} ... {x_0}\) 是一个比特串,如果将 \(X\) 视作是一个无符号数 (unsigned) ,那么有:
\[
B2U(X) = \sum_{i=0}^{w-1}x_i \cdot 2^i
\]
如果将 \(X\) 视作是一个有符号数 (signed) ,那么有:
\[
B2T(X)= -x_{w-1} \cdot 2^{w-1} + \sum_{i=0}^{w-2} x_i \cdot 2^i
\]
下面是这个公式给我带来的几点启示。
特殊值
对于 32 位的 int
,我们知道 int x = 0x80000000
表示的是十进制中的 -2147483648
。
大一上 C 语言的时候,老师当时的解释:最高位表示的是符号位,因此按「道理」来说, 0x00000000
表示 +0
而 0x80000000
表示 -0
,但是 0 只需要一种编码表示就够了,所以把 0x80000000
这个负数的编码「分配」给 -2147483648
。同时也是为了满足计算机的「模运算」系统(大溢出变小,小溢出变大),即:
0x80000000 - 1 = 0x7fffffff
0x7fffffff + 1 = 0x80000000
这种解释对我来说总感觉难以接受,很别扭的感觉。
但是采用上述公式 \(B2T(X)\) 就很好地把所有的二进制编码 统一 映射到我们的十进制数了,整型范围内无一例外。
补码转十进制
假设有 \((10011)_2\) 这个比特串,将其视作有符号数解释。原来没有这个公式,不容易看出对应的十进制数值。原来我是这么转换的:
k = 10011
~k = 01100
~k+1 = 01101 = 13
可得 \(k=-13\) 。
现在有了上述公式,就可以口算:-16 + 3 = -13
。至少对我说,这个效率的提升是显然的(当 \(k\) 有 32 位的时候更加明显)。
补码比较大小
设 \(a=(10011)_{2}\) , \(b=(10101)_{2}\) ,将其均视作有符号数解释。原来我的做法是先转换为十进制,然后再比较大小。但根据上述公式 \(B2T(X)\) ,可以看出,公式的 \(x_{w-1} \cdot 2^{w-1} = -16\) 部分是相等的,所以只需比较后半部分,而 0011 < 0101
,因此显然可以得出 \(a < b\) 。
这就省略了转换十进制的繁琐过程。
符号扩展和位截断
符号扩展,在之前我有一点难以理解。比如:
int8_t x0 = -8; // 1111 1000
int16_t x1 = x0; // 1111 1111 1111 1000
显然,x1
的值也是 -8
,但是我一直很迷惑这其中的原理,怎么证明符号扩展(整数扩展补 0 ,负数扩展补 1)后的值不变?对正数来说是显然的;对于负数,我们可以从其绝对值考虑 abs(k) = ~k + 1
,所以只需要证明 ~k
的值不变即可,这也显然成立。
下面尝试用上述公式证明。设比特串 \(X_1 = x_{n_{1}-1} ... x_0\),现在将 \(X_1\) 扩展为 \(n_2\) 位长的有符号数 \(X_2\) 。
Index | \(n_2 - 1\) | ... | \(n_1 - 1\) | ... | 0 |
---|---|---|---|---|---|
\(X_1\) | \(null\) | \(...null...\) | \(1\) | \(x_{n_1-2} ... x_1\) | \(x_0\) |
\(X_2\) | \(1\) | \(...111...\) | \(1\) | \(x_{n_1-2} ... x_1\) | \(x_0\) |
对于 \(X_1\) :
\[ B2T(X_1) = -2^{n_1-1} + \sum_{i=0}^{n_1-2}x_i \cdot 2^i \]
对于 \(X_2\) :
\[ \begin{align} B2T(X_2) &= -2^{n_2-1} + \sum_{i=0}^{n_2-2}x_i \cdot 2^i \\ &= -2^{n_2-1} + \sum_{i=n_1-1}^{n_2-2}2^i + \sum_{i=0}^{n_1-2}x_i \cdot 2^i \\ &= -2^{n_2-1} + \frac{2^{n_1-1}(1-2^{n_2-n_1})}{1-2} + \sum_{i=0}^{n_1-2}x_i \cdot 2^i \\ &= -2^{n_1-1} + \sum_{i=0}^{n_1-2}x_i \cdot 2^i \\ &= B2T(X_1) \end{align} \]
证毕。
对于上述 2 种证明方法,第一种显然更简单,但个人更喜欢第二种,更直观,更暴力(●=◡=●)。
有点可惜,自己本科上过关于编码的理论课程,但我觉得最初搞计算机,设计出「补码」这种编码方式的那些人实在太强大了。「补码」不仅统一了计算机中加减法的运算,而且对于 C 语言中符号扩展,位截断这些处理都变得十分简单。
对于「位截断」来说:
int16_t a = -99;
int8_t b = a;
证明 b
的值仍然是 -99 的过程就是上述证明的逆过程。
标签:...,符号,二进制,sum,B2T,补码,再谈,cdot 来源: https://www.cnblogs.com/sinkinben/p/12377232.html
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。