前言
学习二进制及相关运算是计算机学习的重要部分。为了解决我在学习汇编时遇到的问题,我想在这篇博客中梳理并巩固之前所学习的二进制内容,从而更好的往下学习。
进制
进位制又称进制,是一种记数方式,亦称位置记法(positional notation)、数字命位法、定位记法、进位记数法、位值记数法(place-value notation)、位置数值系统(positional numeral system);利用这种“记数法”,可以使用有限种“数字符号”来表示所有的数值。
— wikipedia
我们日常生活中使用十进制进行计算,即为逢十进一,因为一个正常人的手指头为十根,方便我们计数。但是我们在学习其他进制时不需要割去自己的手指,或用上我们的脚趾。
古巴比伦人使用六十进制计算,时至今日仍用作记录 时间, 角度 等。
一进制以1为底数, 与其他进制不同,一进制只有数字 ‘1’, 无法恰当的表示数字0。
而计算机使用二进制, 以 0和1 表示, 方便电路信号模拟数字。
进制转换
我们计算时使用十进制,但计算机使用二进制。为了与计算机更好的交流,我们需要使用到进制转换:
N进制 -> 十进制:
譬如将二进制数转化为十进制数,将二进制数每一位乘上位权,再乘上这一位的数字,最后相加得到十进制数:
同样的:
十进制->N进制:
将十进制数转换为其他进制数,很方便的一个方法是短除法。
例如将十进制转换为二进制:
1 | 185 / 2 = 92 ...... 1 |
将余数从下向上读,
从而我们得到了
同样的:
1 | 185 / 8 = 23 ...... 1 |
所以有
数字的存储方式
我们使用十进制运算,而计算机使用二进制。所以我们需要将数字以二进制的形式存入计算机。
显然,对于一个正数或一个无符号数,我们只需要将其转化为二进制存入计算机即可
但对于一个负数,我们需要一些特殊的办法使其能够存入计算机,并且能够正常运算。
位(bit)
位是信息的最小单位,一位只能表示 0或1。
符号位和原码
为了表示一个二进制数的正负,我们令这个数字的第一位为符号位,0即为正,1即为负。符号位只表示这个数的正负,不改变它的绝对值。
例如,
这样我们可以用二进制表示一个负数。
同样:
这里的1000 0011和0000 0011都为原码,原码即未经更改的码。
反码
原码只能用来表示一个数,不能直接进行参与运算,原码的符号位必须与其他位分离开,这样做增加了硬件的开销和复杂性。
对于一个简单的 $1 + (-1) = 0$
当我们使用原码计算:
显然我们得到了一个错误答案。
为了进行正确的运算,反码应运而生,一个正二进制数的反码即为它本身,而对于一个负数,它的反码为将它除符号位以外所有的位取反得到的结果。
例如:
对于运算$1 + (-2) = -1$ :
补码
运用反码,我们已经基本可以进行正确的运算了。但是对于之前提到的运算$1 + (-1) = 0$ :
因为1111 1111的符号位为1,所以得到最终结果为-0,但我们又知道0的符号是没有意义的,所以为了解决0的两个编码的问题,我们引入了补码。
正数的补码是它本身,而负数的补码是它的反码 + 1。
例如:
此时我们计算$1 + (-1) = 0$ :
从而,我们在解决0的不同编码问题的同时,得到了正确答案。
同时,我们用1000 0000表示-128而非-0。
所以用补码存储数据时,一个八位数的数据范围为$[-128, 127]$,而当用原码或反码存储时,数据范围为$[-127, 127]$ 。
位运算
位运算对我们来说可能比较麻烦,但对计算机来说,一次位运算要进行的操作数远少于一次加法或其他运算。
与(and, &):
对两个二进制的每一位进行运算, 当两位数都为1时,运算结果为1, 否则为0。
或(or, |):
对两个二进制的每一位进行运算, 当两位数都为0时,运算结果为0,否则为1。
亦或(xor, ^):
对两个二进制的每一位进行运算,当两位数不同时,运算结果为1,否则为0。
非(not,~):
对一个二进制数的每一位,将1变为0, 0变为1。
左移(shl, <<):
将二进制数补码每一位左移一定位数,位移舍弃最高位,低位补0,对二进制数左移一次相当于将这个数乘2。
右移(shr, >>):
与左移相反,向右位移,舍弃低位,高位补符号位。对二进制数右移一次相当于将这个数除以2,向下取整
(ps:c++中整数除法也会自动取整,但 ‘ \ ‘为向零整除,右移运算为向下整除)