前言:学习了一段时间的计算机基础后,笔者现在对计算机的二进制有一定的见解,特地把我的看法分享出来,供大家交流讨论。
一,关于进制
众所周知,我们人类最常用的就是十进制了,是用1,2,3,4,5,6,7,8,9这9个数字来表示数量的,满10进1,以此类推,我们来说明一下二进制,八进制,十六进制的情况。
二进制:0,1,满2进一
八进制:0,1,2,3,4,5,6,7,满8进一
十六进制:0,1,2,3,4,5,6,7,8,9,A(10),B(11),C(12),D(13),E(14),F(15)满十六进一
每一个进制满足高一位是多该进制的整数倍。如10,100,1000;2,4,8,16;8,64,512……
依照这个不同的进制,我们可以轻松得到不同进制之间的换算关系。
如除二取余法,这些方法都是从同余角度来换算的,具体例子大家可以去自行练习。
二,不同进制的优劣及应用场合:
十进制
从我们最熟悉的十进制来说,我们最早用十进制的重要原因很可能就是我们有10根手指,这样数数要方便许多,最早的计数方式如结绳记事等等,肯定是不如掰手指头方便的,如果人是4根指头可能就是四进制了。
二进制
从二进制来讲,这个主要用在计算机上,那么有这样类似的问题:为什么不也用三进制或者10进制这样的?这主要取决于计算机的运算逻辑——布尔逻辑。计算机本质上还是通过逻辑电路来实现的,当然,类似巴贝奇差分机等古老的用齿轮做驱动的,当然可以用其他的进制,但随着电学的发展,电子管计算机计算性能,体积上要有明显优势。
现在我们来比较一下10进制和2进制:
从数字的种类上来讲:
10进制需要9个不同数字来运行,对于电脑来说可以设计出9种不同的电子管,但是电脑使用发光二极管,所以从数字数量上就不占优势。
从储存数字内存上来讲:
内存即是字节(B),比特(bit)
举一个例子:10位二进制:
0000 0000 00 总共有2的10次方的可能性,即1024,接近1000
用二进制,用2*10个电子管就行,用10进制,用3*10个物理装置,显然二进制对电脑的配置更少一些
在更大的数字下,如2的33次方,对应着10的10次方
二进制用66个,10进制用100个,高下立判。
二进制的漏洞:
二进制有一个较为致命的问题,我们来举个例子:
在换算时:
(1000.0110)换算为10进制:1*8+0.25*1+0.125*1=8.375
把47.4换算成2进制:整数部分:0010 1111,小数部分:0.01111……
47.4换算小数部分时是不可以得出精确值的,其实,小数部分只有为25,625,875……素数中只能被5做质因数分解的才可以精确表示,以此类推,3进制,5进制也有这个问题,所有进制都有这样的缺陷。
对于8进制与16进制:
不难看出,8和16分别是2的3次方和4次方,我们可以用这两种进制来缩短字节长度,也就是内存,如ip地址的分配,现有ipv4,ipv6的,ipv6就是用16进制写成。
16进制:A-F,1-10;
三,再谈二进制:
计算机使用二进制,具体的计算流程或是运算逻辑是怎样的呢?
原码
原码:当计算机处理两个数的加减时,相加是容易的,但相减就要困难一些,归根到底是因为计算机对于负号这一符号不好定义,于是我们规定,对于一个一般的二进制序列,我们采用8位,也就是8比特,两字节。在最左侧的一位上为符号位,0表示正数,1表示负数。
例子:1000 0010:-2 0010 0010 :34
我们把这样最初的编码叫做原码。
原码有一些弊端,如表示范围,对于一个8位二进制数,能表示[-127,127],即1111 1111 0111 111
如果运算结果超过这一数字,就会溢出,也就是报错。
为了方便计算机的运算,科学家们发明了反码和补码,当然,反码只是过渡,补码才是在重头戏。
补码:
表示范围:对于8为二进制数:能表示[-128,127],因为补码把原码中1000 0000和0000 0000 这两个值赋予了不同的数字,1000 0000在补码中表示-128,0000 0000在补码中表示0,而原码中都表示0.
反码的得出:
正数:补码=反码=原码
负数:符号位不变,其他位数与原码取反
补码的得出:
正数:补码=反码=原码
负数:补码=原码取反+1,反之,原码=补码取反+1,大家可以举几个例子自己验证一下,这里不在证明。
例子:0000 1100(原码):12 原码:1001 0110:-22
反码:0000 1100 反码:1110 1001
补码:0000 1100 补码:1110 1001+1(注意进位):1110 1010
计算12-22:
补码:0000 1100+1110 1010=1111 0110
例子:补码运算:0011 1100+1100 0100:(60-59)
观察到:运算完应是1 0000 0000,多了一位,这样就要联系同余理论了,我们可以把符号位的进位忽略掉,其他保持不变,即0000 0000 ,取反加一,0000 0001=1
溢出
溢出:对于8位二进制数,补码表示范围:[-128,127],如果正数与正数相加超过这个临界值,称为正溢出,负数与负数相加称为负溢出。
溢出是很危险的,需要根据项目不同及时调整位数,可以采用16位的等等。
四,二进制规格化浮点数
由上述讨论,二进制的小数部分是比较麻烦的。
与十进制类似,二进制也有科学计数法,也就是二进制规格化浮点数。
N=M×R的E次方
N是原数,M是尾数,R表示进制,E表示阶码
例子:26.375=11010.011=0.1101 0011×2的101次方(101为二进制)
表示为如下16位位模式表示浮点数:
0000 101 011010011
第一个数是阶符,0表示正数,
000101表示阶码,即101
后面的0表示数符,类似小数点
11010011表示尾数
对于二进制浮点数来讲,小数点的表示是有门道的,上述展示的是一种方法,还有一种规定好在哪里点小数点的,但那样的方法比较笨重,这里就不再赘述。
类似于上述浮点表示法,如果阶码多几位,导致后面11010011变成 0.0011010011,会使得最后两位无法进入16为位模式,于是,规定了一种规格化:
要求尾数部分针织最高位为1
后记:先暂时就这么多,后续再补充,可以补充一些同余方面的知识等等。。。
代码实现多进制转换
先挖个坑,有空再补。。。
五,进制转换的代码实现
这里先贴一个C版本的代码
#include<stdio.h>
int main()
{
char w[6]= {'A','B','C','D','E','F'};
char a[1000];
int n,r,i,z;
while(~scanf("%d %d",&n,&r))
{
i=0,z=0;
if(n<0)
{
n=-n;
z=1;
}
while(n)
{
a[i++]=n%r;
n/=r;
}
if(z==1)
printf("-");
for(int j=i-1; j>=0; j--)
{
if(a[j]>9)
printf("%c",w[a[j]-10]);
else
printf("%d",a[j]);
}
putchar('\n');
}
return 0;
}