数据在计算机中的存储形式和运算
⼀、数据概述
以为例,⾥⾯所有的基本数据类型,都是以符合⼈类世界和⾃然世界的逻辑⽽出现的。⽐如说int,bool,float等等。这些数据类型出现的⽬的,是更于让⼈容易理解,可以说,这些数据类型是架通⼈类思维 与 计算机的桥梁。
我们知道。依照冯诺依曼体系,计算机中并没有这些int  float等等,⽽全部都是0和1表⽰的⼆进制数据,并且计算器只能理解这些0和1的数据。所以说,所有的数据在计算机⾥⾯都是以0和1存储和运算的,这是冯诺依曼体系的基础。因此,符合我们⼈类思维的数据都要通过⼀定的转换才能被正确的存储到计算机中。
⼆、进制
要想理解数据的存储,⾸先要明⽩最基本的⼆进制问题,因为,这是计算机中数据最基本的形式,⾸先看下⾯的问题:
1、什么是⼆进制?进制的概念?
2、计算机中为什么要⽤⼆进制?
3、⼆进制和符合⼈类思维的⼗进制之间的关系?
4、为什么⼜会出现⼋进制、⼗六进制?
5、所有进制之间的转换?
(1)、进制的概念
进制也就是进位制,是⼈们规定的⼀种进位⽅法。 对于任何⼀种进制---X进制,就表⽰某⼀位置上的数运算时是逢X进⼀位。 ⼗进制是逢⼗进⼀,⼗六进制是逢⼗六进⼀,⼆进制就是逢⼆进⼀
在采⽤进位计数的数字系统中,如果只⽤r个基本符号表⽰数值,则称为r进制(Radix-r Number System),r称为该数制的基数(Radix)。不同的数制的共同特点如下:
(1)、每⼀种数制都有笃定的符号集。例如,⼗进制数制的基本符号有⼗个:0,1,2。。。,9。⼆进制数制的基本符号有两个:0和1.(2)、每⼀种数制都使⽤位置表⽰法。即处于不同位置的数符所代表的值不同,与它所在位的权值有关。
例如:⼗进制1234.55可表⽰为
1234.55=1×10^3+2×10^2+3×10^1+4×10^0+5×10^(-1)+5×10^(-2)
可以看出,各种进位计数制中权的值恰好是基础的某次幂。因此,对任何⼀种进位计数制表⽰的数都可以写成按权展开的多项式。
(2)、计算机中为什么要⽤⼆进制
电脑使⽤⼆进制是由它的实现机理决定的。我们可以这么理解:电脑的基层部件是由集成电路组成的,这些集成电路可以看成是⼀个个门电路组成,(当然事实上没有这么简单的)。
当计算机⼯作的时候,电路通电⼯作,于是每个输出端就有了电压。电压的⾼低通过模数转换即转换成了⼆进制:⾼电平是由1表⽰,低电平由0表⽰。也就是说将模拟电路转换成为数字电路。这⾥的⾼电平与低电平可以⼈为确定,⼀般地,2.5伏以下即为低电平,3.2伏以上为⾼电平
电⼦计算机能以极⾼速度进⾏信息处理和加⼯,包括数据处理和加⼯,⽽且有极⼤的信息存储能⼒。数据在计算机中以器件的物理状态表⽰,采⽤⼆进制数字系统,计算机处理所有的字符或符号也要⽤⼆进制编码来表⽰。⽤⼆进制的优点是容易表⽰,运算规则简单,节省设备。⼈们知道,具有两种稳定状态的元件(如晶体管的导通和截⽌,继电器的接通和断开,电脉冲电平的⾼低等)容易到,⽽要到具有10种稳定状态的元件来对应⼗进制的10个数就困难了
1)技术实现简单,计算机是由逻辑电路组成,逻辑电路通常只有两个状态,开关的接通与断开,这两
种状态正好可以⽤“1”和“0”表⽰。(2)简化运算规则:两个⼆进制数和、积运算组合各有三种,运算规则简单,有利于简化计算机内部结构,提⾼运算速度。(3)适合逻辑运算:逻辑代数是逻辑运算的理论依据,⼆进制只有两个数码,正好与逻辑代数中的“真”和“假”相吻合。(4)易于进⾏转换,⼆进制与⼗进制数易于互相转换。(5)⽤⼆进制表⽰数据具有抗⼲扰能⼒强,可靠性⾼等优点。因为每位数据只有⾼低两个状态,当受到⼀定程度的⼲扰时,仍能可靠地分辨出它是⾼还是低。
(3)、⼋进制和⼗六进制出现是为什么
⼈类⼀般思维⽅式是以⼗进制来表⽰的,⽽计算机则是⼆进制,但是对于编程⼈员来说,都是需要直接与计算器打交道的,如果给我们⼀⼤串的⼆进制数。⽐如说⼀个4个字节的int型的数据:0000 1010 1111 0101 1000 1111 11111 1111,我想任何程序员看到这样⼀⼤串的0、1都会很蛋疼。所以必须要有⼀种更加简洁灵活的⽅式来呈现这对数据了。
你也许会说,直接⽤⼗进制吧,如果是那样,就不能准确表达计算机思维⽅式了(⼆进制),所以,出现了⼋进制、⼗六进制,其实⼗六进制应⽤的更加⼴泛,就⽐如说上⾯的int型的数据,直接转换为⼋进制的话,32./3 余2 也就是说  ,我们还要在前⾯加0,但是转换为⼗六进制就不同了。32/4=8,直接写成⼗六进制的8个数值拼接的字符串,简单明了。
所以说⽤⼗六进制表达⼆进制字符串⽆疑是最佳的⽅式,这就是⼋进制和⼗六进制出现的原因。
(4)、进制间的相互转换问题
常⽤的进制有⼆进制、⼗进制、⼋进制和⼗六进制
①、⼋进制、⼗六进制、⼆进制-------------->⼗进制
都是按权展开的多项式相加得到⼗进制的结果。
⽐如
⼆进制1010.1到⼗进制:1×2^3  +  0×2^2  +  1×2^1  +  0×2^0  +  1×2^(-1)=10.5
⼋进制13.1到⼗进制:1×8^1  +  3×8^0  +  1×8^(-1)=11.125
⼗六进制13.1到⼗进制:1×16^1  +  3×16^0  +  1×16^(-1)=19.0625
②、⼗进制-------------->⼋进制、⼗六进制、⼆进制
都是按照整数部分除以基数(r)取余,⼩数部分乘以基数(r)取整
⼗进制10.25 到⼆进制:整数部分除2,⼀步步取余。⼩数部分乘2,⼀步步取整
⼋进制到⼗进制,⼗六进制到⼗进制都是和上⾯的⼀样,只不过不在是除2乘2,⽽是8或者16了,这是根据⾃⼰的基数来决定的。
③、⼆进制<------------->⼋进制、⼗六进制
⼆进制转换成⼋进制的⽅法是:从⼩数点起,把⼆进制数每三位分成⼀组,⼩数点前⾯的不够三位的前⾯加0,⼩数点后⾯的不够三位的后⾯加0,然后写出每⼀组的对应的⼗进制数,顺序排列起来就得到所要求的⼋进制数了。
依照同样的思想,将⼀个⼋进制数的每⼀位,按照⼗进制转换⼆进制的⽅法,变成⽤三位⼆进制表⽰的序列,然后按照顺序排列,就转换为⼆进制数了。
⼆进制数10101111.10111转换为⼋进制的数:(010  101  111.101  110)=  2  5  7.5  6=257.56
⼋进制数257.56转换为⼆进制的数:2  5  7.5  6  =(010  101  111.101  110)=10101111.101
⼆进制转换到⼗六进制差不多:从⼩数点起,把⼆进制数每四位分成⼀组,⼩数点前⾯的不够四位的前⾯加0,⼩数点后⾯的不够四位的后⾯加0,然后写出每⼀组的对应的⼗进制数,然后将⼤于9的数写成如下的形式,10---->A,11-->B,12---->C,13----->D,14----->E,15---->F,在顺序排列起来就得到所要求的⼗六进制了。
同样,将⼀个⼗六进制数的每⼀位,按照⼗进制转换⼆进制的⽅法,变成⽤四位⼆进制表⽰的序列,然后按照顺序排列,就转换为⼆进制数了。
⼆进制数  10101111.10111转换为⼗六进制的数:(1010  1111.1011 1000)=A  F.B  8=AF.B8
⼗六进制AF.B8转换为⼆进制:  A    F.B  8=(1010  1111.1011  1000)=10101111.10111
三、数据的分类
学过编程知识的同学肯定知道,特别是⾯向对象的,数据类型⼀般分类基本数据类型  和 复合数据类型。其实从本质上将,复合数据类型也是由基本数据类型构成的。所以,这⾥先只讨论基本数据类型的存储情况。
以C语⾔为例,基本数据类型包括,⽆符号整形,带符号整形,实型,char型,有朋友说还有bool,其实在C语⾔中bool类型也还是整形数据,只不过是⽤宏声明的⽽已,不明⽩的可以看这篇⽂章:
1、先看⽆符号整形
⽆符号整形在数据中的存储⽆疑是最⽅便的,因为没有符号位,只表⽰正数,所以在存储计算⽅⾯都很简单。⽆符号整形在就是以纯粹的⼆进制串存储在计算机中的。
⽐如说看下⾯的例⼦:
从输出的⼗六进制数中可以看出,它就是以直接的⼆进制
数表⽰的。
2、在看带符号整形
对于带符号数,机器数的最⾼位是表⽰正、负号的符号位,其余位则表⽰数值。
先不谈其他的问题,只谈⼆进制表达数据的问题(我也不知道怎么说),看下⾯的例⼦:
假设机器字长为8的话:
⼀个⼗进制的带符号整形 1,表达为⼆进制就是 (0000 0001)
⼀个⼗进制的带符号整形 -1,表达为⼆进制就是 (1000 0001)
那么,两者相加 ,⽤⼗进制运算 1+(-1)=0
在看⼆进制运算  (0000 0010)+(1000 0001)=(1000 0010)    这个数转换为⼗进制结果等于-2。
可以发现出问题了,如上所表⽰的⽅式,就是今天所要讲的原码。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
①、原码
数值X的原码记为[x]原,如果机器字长为n(即采⽤n个⼆进制位表⽰数据)。则最⾼位是符号位。0表⽰正号,1表⽰负号,其余的n-1位表⽰数值的绝对值。数值零的原码表⽰有两种形式:[+0]原=0000 0000  ,-[0]原=1000 0000.
例⼦:若机器字长n等于8,则
[+1]原=0000 00001          [-1]原=1000 00001
[+127]原=0111 1111          [-127]原=1111 1111
[+45]原=0010 1101          [-45]原=1010 1101
可见,原码,在计算数值上出问题了,当然,你也可以实验下,原码在计算正数和正数的时候,它是⼀点问题都没有的,但是出现负数的时候就出现问题了。所以才会有我下⾯将的问题:反码
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
②、反码
数值X的反码记作[x]反,如果机器字长为n,则最⾼位是符号位,0表⽰正号,1表⽰负号,正数的反码与原码相同,负数的反码则是其绝对值按位求反。数值0的反码表⽰有两种形式:[+0]反=0000 0000  ,-[0]反=1111 1111.
例⼦:若机器字长n等于8,则
[+1]反=0000 00001          [-1]反=1111 1110
[+127]反=0111 1111          [-127]反=1000 0000
[+45]反=0010 1101          [-45]反=1101 0010
在看反码计算的问题:
1+(-1)=0              |            (0000 0001)反+(1111 1110)反=(1111 1111)反=(1000 0000)原=【-0】  可以看到,虽然是-0,但是问题还不是很⼤
1+(-2)=-1              |            (0000 0001)反+(1111 1101)反=(1111 1110)反=(1000 0001)原=【-1】  可以看到,没有问题
-1+(2)=1              |            (1111 1110)反+(0000 0010)反=(0000 0000)反=(0000 0000)原=【0】  可以看到,问题发⽣了,因为溢出,导致结果变为0了。
所以,看以看到,⽤反码表⽰,问题依然没有解决,所以,出现了下⾯的补码
③、补码
数值X的补码记作[x]补,如果机器字长为n,则最⾼位是符号位,0表⽰正号,1表⽰负号,正数的补码与原码反码都相同,负数的补码则等于其反码的末尾加1。数值0的补码表⽰有唯⼀的编码:[+0]补=0000 0000  ,-[0]补=0000 0000.
例⼦:若机器字长n等于8,则
[+1]补=0000 00001          [-1]补=1111 1111
[+127]补=0111 1111          [-127]补=1000 0001
[+45]补=0010 1101          [-45]补=1101 0011
在看补码计算的问题:
1+(-1)=0              |            (0000 0001)补+(1111 1111)补=(0000 0000)补=(0000 0000)原=【0】  可以看到。没有问题
1+(-2)=-1              |            (0000 0001)补+(1111 1110)补=(1111 1111)补=(1000 0001)原=【-1】  可以看到,没有问题
-1+(2)=1              |            (1111 1111)补+(0000 0010)补=(0000 0001)补 =(0000 0001)原=【1】  可以看到,没有问题
通过上⾯的计算,我们发现,⽤补码的⽅式,就不存在在原码和反码中存在的计算问题了。其实,这也是计算机表达带符号整数⽤补码的原因。如果,你觉得我举得例⼦太少,缺少代表⾏,你可以⾃⼰试试。不过,放⼼补码⼀定是不会存在原码和反码的问题的。
讨论下原码反码补码的原理,没兴趣的同学可以跳过 。不过我觉得从本质上了解补码的机制还是很有好处的。
1、为什么原码不⾏?
( 1 ) 10-  ( 1 )10 =  ( 1 )10 + ( -1 )10 =  ( 0 )10
(00000001)原 + (10000001)原 = (10000010)原 = ( -2 ) 显然不正确.
通过上⾯原码计算式可以看出,当正数加上负数时,结果本应是正值,得到的却是负值(当然也有可能得到的是正数,因为被减数与减数相加数值超过0111 1111,即127,就会进位,从⽽进位使符号位加1变为0了,这时结果就是正的了)。⽽且数值部分还是被减数与减数的和。
并且,当负数加上负数时(这⾥就拿两个数值部分加起来不超过0111 1111的来说),我们可以明显看出符号位相加变为0,进位1被溢出。结果就是正数了。
因此原码的错误显⽽易见,是不能⽤在计算机中的。
2、补码的原理
既然原码并不能表⽰负数的运算问题,那么当然要另想他法了。这个⽅法就是补码,关于补码是如何提出的,我并不知道,但不得不说,这是⼀个最简洁的⽅法,当然,也可以⽤别的更复杂的⽅法,那就不是我们想要的了。
我⾃⼰研究补码的时候,也在⽹上了些资料,都是到处copy,反正我是看的迷糊了,本⼈数学功底不怎么样,看不懂那些⼤神写的,只好,⾃⼰理解了下。
要谈补码,先看看补数的问题。什么是补数,举个简单的例⼦,100=25+75。100⽤数学来说就是模M,那么就可以这样概括。在
M=100的情况下,25是75的补数。这就是补数。
25是75的补数,这是在常规世界中,在计算机上就不是这样了,因为,在计算机中,数据存在这溢出的问题。反码计算器在线
假设机器字长是8的话,那么能表达的最⼤⽆符号数就是1111 11111,在加1的话,就变成1  0000  0000 ,此时因为溢出,所以1去掉,就变成0了,这个很简单,相信学计算机的⼈都会明⽩。
也就是说,在计算机中,补数的概念稍微不同于数学之中,25+75=100,考略计算机中的溢出问题,那么25+75就等于0了。也就是说,25和75不是互为补数了。
我觉得⽤闹钟来⽐喻这个问题在形象不过了,因为闹钟也存在着溢出的问题,当时间到达11:59 ,在加1分钟的话就变成0:0了,这和计算机的溢出是同⼀个道理。
那么,有⼀个时钟,现在是0点,我想调到5点,有两种⽅法,⼀个是正着拨5,到5点。第⼆种⽅法是倒着拨7,也可以到5点。正着拨5记作+5,倒着拨7,记作-7,⽽闹钟的M是12,也就是说,在考略溢出的情况下,M=12,5是-7的补数。⽤个数学等式可以这样表达
0+5=0+-7,即0+5=0-7
这就是计算机中的数值计算和数学中的计算不同的地⽅。
明⽩了计算机中补数的道理,那么就明⽩补码的问题了。还是⽤例⼦说明:
在计算机中计算⼗进制 1+(-2)。
1的原码是:0000 0001
-2的原码是:1000 0010
-2的补码是:1111 1110  这个⼆进制换做⽆符号的整数⼤⼩就是254,⽽8位⼆进制数的M=2^8=256。(很多⽂章中把M写成2^7,这根本就是不对的,根本没有解决符号位的问题)
你发现什么了没,当换成补码后,-2和254就是补数的关系。
也就是1+(-2)  等价于了 1+254了。
这样做,好处在什么地⽅,你⾃⼰都可以看得到:
①、利⽤补数和溢出的原理,减法变成了加法
②、符号位不在是约束计算的问题,不会存在原码中的问题了,因为变成补码后,虽然最⾼位依然是1,但是这个1就不在是最为符号位了,⽽是作为⼀个普通的⼆进制位,参与运算了。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。