二进制世界

584 阅读10分钟

这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战

  在生活中,我们通常都是使用阿拉伯数字计数的,也就是10进制,以10为单位,遇10进一,所以是由0,1,2、3、4、5、6、7、8、9组成的;而在计算机中,计算机是无法识别10进制数的,它只能识别01代码,也就是二进制,由0、1两位数字组成,逢二进一

  计算机内部是一个二进制的数字世界,一切信息的存储、处理、传输,都是以二进制编码进行的。

用二进制表示计算机信息的原因

  计算机的内部由各种集成电路(IC)构成,IC上两侧并排排列着许多引脚,用以信息的传输。在引脚上传输的电信号,为了方便实现,只保留高电压和低电压两种状态(一般为0V和+5V)。也就是说,IC上的一个引脚,只表示两种状态。而恰好,二进制与IC的特性非常吻合。(计算机也不一定要使用二进制进行实现信息存储与计算,比如战斗民族俄罗斯就有一个三进制计算机工程)

  采用二进制进行计算,数字将被转化为只有01的形式,比如十进制的4转换为二进制就是101。而这里面,每一位的01我们给它一个单位为位(bit,也称为比特、比特位)是计算机处理信息的最小计量单位

香农在莫尔学院听讲座的过程中,了解到了二进制对于计算机的重要价值:二进制数不仅是计算机内各个部件交换信息的重要载体,还是确保信息存储和检索过程不出差错的重要工具。后来,香农发明了术语“比特(bit)”来指代二进制数位(binary digit ),同时阐述了如何利用比特来衡量信息量,并对信息进行传输、加密、压缩、纠错。

  但是这个单位太小,信息转化位二进制之后,整个信息的位数会比其他进制大很多,所以不适合用于做为常用的计量单位。我们把每8位二进制数做为一个集合,8位二进制数被称为一个字节(byte),字节是最基本的信息计量单位。这也是二进制数的位数一般是8位、16位、32位……也就是8的倍数的原因。位是最小单位,字节是基本单位

  用字节单位处理信息时,如果数字小于存储数据的字节数,那么就在高位补0。例如,100111这个6位二进制数,用8位(=1字节)表示时为00100111,用16位(=2字节)表示时为0000000000100111。

  在计算机的最底层,实现各种程序的机器语言(也即二进制代码),除了表示数字信息,还能存储文字、声音、图像以及各种处理指令。对于用二进制数表示的信息,计算机不会区分它是数值、文字,还是某种图片的模式等,而是根据编写程序的各位对计算机发出的指示来进行信息的处理(运算)。例如00100111这样的二进制数,既可以视为纯粹的数值作加法运算,也可以视为'(单引号)文字而显示在显示器上,或者视为■■□■■□□□这一图形模式印刷出来。具体进行何种处理,取决于程序的编写方式。

二进制与十进制

  位权的含义。例如,十进制数39的各个数位的数值,并不只是简单的3和9,这点大家应该都知道。3表示的是3×10=30,9表示的是9×1=9。这里和各个数位的数值相乘的10和1,就是位权。数字的位数不同,位权也不同。

高位与低位

  高位、低位的概念是根据数字的位权来定义的。比如二进制1010,从左往右,第一个1的位权是3,它相对于其他的位就是高位;第一个0的位权是2,它相对第一个1是低位,相对于后面两位数字就是高位。一般而言,我们把最左端称为高位,最右端称为低位。

二进制转十进制

方法:二进制数从低位到高位(即从右往左)计算,第0位的权值是2的0次方,第1位的权值是2的1次方,第2位的权值是2的2次方,依次递增下去,把最后的结果相加的值就是十进制的值了。

例:将二进制的101011转换为十进制的步骤如下:

  1. 第0位 1 x 2^0 = 1;
  2. 第1位 1 x 2^1 = 2;
  3. 第2位 0 x 2^2 = 0;
  4. 第3位 1 x 2^3 = 8;
  5. 第4位 0 x 2^4 = 0;
  6. 第5位 1 x 2^5 = 32;
  7. 读数,把结果值相加,1+2+0+8+0+32=43,即101011(二进制)=43(十进制)。

十进制转二进制

方法:除2取余法,即每次将整数部分除以2,余数为该位权上的数,而商继续除以2,余数又为上一个位权上的数,这个步骤一直持续下去,直到商为0为止,最后读数时候,从最后一个余数读起,一直到最前面的一个余数。

例:将十进制的43转换为二进制的步骤如下:

  1. 将商43除以2,商21余数为1;
  2. 将商21除以2,商10余数为1;
  3. 将商10除以2,商5余数为0;
  4. 将商5除以2,商2余数为1;
  5. 将商2除以2,商1余数为0;
  6. 将商1除以2,商0余数为1;
  7. 读数,因为最后一位是经过多次除以2才得到的,因此它是最高位,读数字从最后的余数向前读,101011,即43(十进制)=101011(二进制)。

原码、反码、补码

  • 原码:是最简单的机器数表示法,用最高位表示符号位,其他位存放该数的二进制的绝对值。 例如

    • 5的原码是0101
    • -5的原码是1101
  • 反码:正数的反码还是等于原码;负数的反码就是它的原码除符号位外,按位取反。 例如

    • 5的反码与原码相同,为0101
    • -5的反码为1010
  • 补码:正数的补码等于它的原码;负数的补码等于反码+1。 例如

    • 5的补码与原码相同,为0101
    • -5的补码为1011

计算机减法实现

  计算机在做减法运算时,实际上内部是在做加法运算,也即使用加法运算来实现减法运算。

  补码的设计,就是用正数来表示负数。使用补码进行运算时,将符号为看作数值的一部分参与运算,运算结果高位溢出的直接忽略。结果仍然表示一个补码数,高位第一位的作为符号位。

  如1+(-1)=0,二进制计算过程为00000001+11111111确实为0(=00000000)。

左移运算

  移位运算指的是将二进制数值的各数位进行左右移位的运算。移位有左移(向高位方向)和右移(向低位方向)两种。在一次运算中,可以进行多个数位的移位操作。在C++语言(其他很多语言)中,使用运算符<<表示左移运算,>>表示右移运算。

  当进行左移运算时,将二进制数值的各个位数向高位方向移动若干个位置,低位空出的位置补0,高位溢出的数字直接丢弃就可以了,如图所示。每左移一位,相当于原本的数字 × 2。

left-shift.png

移位操作并非二进制特有的,其他进制一样可以使用移位运算。比如十进制,将十进制数字59左移一个位,左移之后个位数空出补上0,那么移位后的结果为590,等于原数字59 × 10。同样,二进制的位权为2,则每左移一位,结果等于原值 × 2。其实,反复思考几遍后就会发现确实如此。十进制数左移后会变成原来的10倍、100倍、1000倍……同样,二进制数左移后就会变成原来的2倍、4倍、8倍……反之,二进制数右移后则会变成原来的1/2、1/4、1/8……这样一来,我们就可以使用移位运算来代替乘法运算和除法运算。

  移位运算也不是随意想移动多少位就移动多少位,我们知道,C++中,int类型数据的长度位32位。那么如果我将一个数字左移33位会发生什么。答案就是结果变成了0。如刚刚所说,我们左移运算时,低位补0高位溢出直接丢弃,那么左移33位时,相当于原数字所有位数都溢出丢弃,此时结果只剩下低位补的0,所以最终得到结果为0。当我们试图移位超过数据类型的长度时,此时代码能够通过编译,但同时编译器会发出移位溢出警告**waring: left shift count >= width of type [-Wshift-count-overflow]** 。

  使用移位运算来替代乘法运算和除法运算,一个最大的优点就是程序速度大大加快。当程序经行乘法运算或者除法运算时,计算机底层使用的加法和移位实现。比如计算9*2,则将2个9进行加法运算,配合移位得到结果18。而如果用移位运算,则只需完成一次左移运算。如果要计算9*8,采用乘法方案,计算机底层要完成7次的加法+移位运算,采用移位运算则只需完成一次左移3位运算。

采用加法的方式实现乘法和除法也是源于生活的智慧。想想我们小学初次学习乘法运算时,是怎么去理解乘法的。同样也是先用加法实现乘法,然后再引入乘法的概念。如果没有了九九乘法表,那么我们日常生活乘法运算估计也是会停留在使用加法完成乘法运算阶段。

右移运算

  了解了左移运算以及补码的概念之后,对于右移运算的理解就简单很多了。首先明白一点,对于计算机的数字,我们统一使用补码的形式进行存储。在右移运算中,将数值从高位往低位移动,同时高位空出补0或者1,低位溢出丢弃。对于高位补充的是0还是1,则根据符号位判定。正数补充0,负数补充1,从而保证数值不会因为高位补充而改变其符号位。

  移位运算同样会带来移位溢出警告**warning: right shift count >= width of type [-Wshift-count-overflow]** ,当右移运算移位超出数据类型的长度时,将得到结果为-1(所有位数变为1,转化为十进制对应为-1)。

左移运算图片代码

digraph digraphs{
    graph [fontname="Microsoft Yahei"];
    edge [fontname="Microsoft Yahei"];
    node [fontname="Microsoft Yahei"];
    label="左移两位运算";
    bgcolor="beige"
    fontcolor="red"
    fontsize=18
    node [shape="record", height=.1]
    node0[label="<f1> 0 | <f2> 0 | <f3> 1 | <f4> 0 | <f5> 0 | <f6> 1 | <f7> 1 | <f8> 1 " xlabel="移位前 = 39"]
    node1[label="<f1> 1 | <f2> 0 | <f3> 0 | <f4> 1 | <f5> 1 | <f6> 1 | <f7> 0 | <f8> 0 " xlabel="移位后 = 156"]
    node2[label="低位补0" shape=plaintext]
    node0:f3 -> node1:f1
    node0:f4 -> node1:f2
    node0:f5 -> node1:f3
    node0:f6 -> node1:f4
    node0:f7 -> node1:f5
    node0:f8 -> node1:f6 [label="左移两位"]
    node1:f7 -> node2
    node1:f8 -> node2
}