专注收集记录技术开发学习笔记、技术难点、解决方案
网站信息搜索 >> 请输入关键词:
您当前的位置: 首页 > 编程

四、数据在计算机中的存储形式和运算

发布时间:2011-07-01 07:30:50 文章来源:www.iduyao.cn 采编人员:星星草
4、数据在计算机中的存储形式和运算

一、数据概述

以C语言为例,里面所有的基本数据类型,都是以符合人类世界和自然世界的逻辑而出现的。比如说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类型也还是整形数据,只不过是用宏声明的而已,不明白的可以看这篇文章:http://blog.csdn.net/lonelyroamer/article/details/7671242


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就不在是最为符号位了,而是作为一个普通的二进制位,参与运算了。


所以,这就是补码的原理所在,通过补数和溢出,解决了减法和负数问题。不知道各位理解了没有,额,反正我是通过这种方法安慰自己的,不知道是不是有失偏颇。



3、在看小数存储的问题





友情提示:
信息收集于互联网,如果您发现错误或造成侵权,请及时通知本站更正或删除,具体联系方式见页面底部联系我们,谢谢。

其他相似内容:

热门推荐: