以C语言为例,里面所有的基本数据类型,都是以符合人类世界和自然世界的逻辑而出现的。比如说int,bool,float等等。这些数据类型出现的目的,是更于让人容易理解,可以说,这些数据类型是架通人类思维 与 计算机的桥梁。
我们知道。依照冯诺依曼体系,计算机中并没有这些int float等等,而全部都是0和1表示的二进制数据,并且计算器只能理解这些0和1的数据。所以说,所有的数据在计算机里面都是以0和1存储和运算的,这是冯诺依曼体系的基础。因此,符合我们人类思维的数据都要通过一定的转换才能被正确的存储到计算机中。
要想理解数据的存储,首先要明白最基本的二进制问题,因为,这是计算机中数据最基本的形式,首先看下面的问题:
1、什么是二进制?进制的概念?
2、计算机中为什么要用二进制?
3、二进制和符合人类思维的十进制之间的关系?
4、为什么又会出现八进制、十六进制?
5、所有进制之间的转换?
进制也就是进位制,是人们规定的一种进位方法。 对于任何一种进制—X进制,就表示某一位置上的数运算时是逢X进一位。 十进制是逢十进一,十六进制是逢十六进一,二进制就是逢二进一
在采用进位计数的数字系统中,如果只用r个基本符号表示数值,则称为r进制(Radix-r Number System),r称为该数制的基数(Radix)。不同的数制的共同特点如下:
①每一种数制都有笃定的符号集。例如,十进制数制的基本符号有十个:0,1,2…,9。二进制数制的基本符号有两个:0和1.
②每一种数制都使用位置表示法。即处于不同位置的数符所代表的值不同,与它所在位的权值有关。
例如:十进制1234.55可表示为
1234.55=1×103+2×102+3×101+4×100+5×10(-1)+5×10(-2)
可以看出,各种进位计数制中权的值恰好是基础的某次幂。因此,对任何一种进位计数制表示的数都可以写成按权展开的多项式。
电脑使用二进制是由它的实现机理决定的。我们可以这么理解:电脑的基层部件是由集成电路组成的,这些集成电路可以看成是一个个门电路组成,(当然事实上没有这么简单的)。
当计算机工作的时候,电路通电工作,于是每个输出端就有了电压。电压的高低通过模数转换即转换成了二进制:高电平是由1表示,低电平由0表示。也就是说将模拟电路转换成为数字电路。这里的高电平与低电平可以人为确定,一般地,2.5伏以下即为低电平,3.2伏以上为高电平
电子计算机能以极高速度进行信息处理和加工,包括数据处理和加工,而且有极大的信息存储能力。数据在计算机中以器件的物理状态表示,采用二进制数字系统,计算机处理所有的字符或符号也要用二进制编码来表示。用二进制的优点是容易表示,运算规则简单,节省设备。人们知道,具有两种稳定状态的元件(如晶体管的导通和截止,继电器的接通和断开,电脉冲电平的高低等)容易找到,而要找到具有10种稳定状态的元件来对应十进制的10个数就困难了
技术实现简单,计算机是由逻辑电路组成,逻辑电路通常只有两个状态,开关的接通与断开,这两种状态正好可以用“1”和“0”表示。
简化运算规则:两个二进制数和、积运算组合各有三种,运算规则简单,有利于简化计算机内部结构,提高运算速度。
适合逻辑运算:逻辑代数是逻辑运算的理论依据,二进制只有两个数码,正好与逻辑代数中的“真”和“假”相吻合。
易于进行转换,二进制与十进制数易于互相转换。
用二进制表示数据具有抗干扰能力强,可靠性高等优点。因为每位数据只有高低两个状态,当受到一定程度的干扰时,仍能可靠地分辨出它是高还是低。
人类一般思维方式是以十进制来表示的,而计算机则是二进制,但是对于编程人员来说,都是需要直接与计算器打交道的,如果给我们一大串的二进制数。比如说一个4个字节的int型的数据:0000 1010 1111 0101 1000 1111 11111 1111,我想任何程序员看到这样一大串的0、1都会很蛋疼。所以必须要有一种更加简洁灵活的方式来呈现这对数据了。
你也许会说,直接用十进制吧,如果是那样,就不能准确表达计算机思维方式了(二进制),所以,出现了八进制、十六进制,其实十六进制应用的更加广泛,就比如说上面的int型的数据,直接转换为八进制的话,32./3 余2 也就是说 ,我们还要在前面加0,但是转换为十六进制就不同了。32/4=8,直接写成十六进制的8个数值拼接的字符串,简单明了。
所以说用十六进制表达二进制字符串无疑是最佳的方式,这就是八进制和十六进制出现的原因。
常用的进制有二进制、十进制、八进制和十六进制
二进制与十进制之间的转换
十进制转二进制
方法为:十进制数除2取余法,即十进制数除2,余数为权位上的数,得到的商值继续除2,依此步骤继续向下运算直到商为0为止。
(具体用法如下图)
二进制转十进制
方法为:把二进制数按权展开、相加即得十进制数。
(具体用法如下图)
二进制与八进制之间的转换
二进制转八进制
方法为:3位二进制数按权展开相加得到1位八进制数。(注意事项,3位二进制转成八进制是从右到左开始转换,不足时补0)。
(具体用法如下图)
八进制转成二进制
方法为:八进制数通过除2取余法,得到二进制数,对每个八进制为3个二进制,不足时在最左边补零。
(具体用法如下图)
二进制与十六进制之间的转换
二进制转十六进制
方法为:与二进制转八进制方法近似,八进制是取三合一,十六进制是取四合一。(注意事项,4位二进制转成十六进制是从右到左开始转换,不足时补0)。
(具体用法如下图)
十六进制转二进制
方法为:十六进制数通过除2取余法,得到二进制数,对每个十六进制为4个二进制,不足时在最左边补零。
(具体用法如下图)
十进制与八进制与十六进制之间的转换
十进制转八进制或者十六进制有两种方法
第一:间接法—把十进制转成二进制,然后再由二进制转成八进制或者十六进制。这里不再做图片用法解释。
第二:直接法—把十进制转八进制或者十六进制按照除8或者16取余,直到商为0为止。
(具体用法如下图)
八进制或者十六进制转成十进制
方法为:把八进制、十六进制数按权展开、相加即得十进制数。
(具体用法如下图)
十六进制与八进制之间的转换
八进制与十六进制之间的转换有两种方法
第一种:他们之间的转换可以先转成二进制然后再相互转换。
第二种:他们之间的转换可以先转成十进制然后再相互转换。
这里就不再进行图片用法解释。
学过编程知识的同学肯定知道,特别是面向对象的,数据类型一般分类基本数据类型 和 复合数据类型。其实从本质上将,复合数据类型也是由基本数据类型构成的。所以,这里先只讨论基本数据类型的存储情况。
以C语言为例,基本数据类型包括,无符号整形,带符号整形,实型,char型,有朋友说还有bool,其实在C语言中bool类型也还是整形数据,只不过是用宏声明的而已,不明白的可以看这篇文章:http://blog.csdn.net/lonelyroamer/article/details/7671242
无符号整形在数据中的存储无疑是最方便的,因为没有符号位,只表示正数,所以在存储计算方面都很简单。无符号整形在就是以纯粹的二进制串存储在计算机中的。
比如说看下面的例子:
从输出的十六进制数中可以看出,它就是以直接的二进制
数表示的。
对于带符号数,机器数的最高位是表示正、负号的符号位,其余位则表示数值。
先不谈其他的问题,只谈二进制表达数据的问题,看下面的例子:
字长是可变的,和当前所使用的语言、语言版本、平台不同都有可能发生变化。例如:
PHP的整数的大小取决于平台,尽管最大值约为20亿是通常的值(32位有符号,PHP不支持无符号整数)。整数大小可以使用常量PHP_INT_SIZE确定,最大值可以使用自PHP 4.4.0和PHP 5.0.5以来的常量PHP_INT_MAX。64位平台的最大值通常约为9E18,除了PHP之前的Windows,它总是32位。
因为这里只讨论整型,所以对浮点型不加赘述。但要注意的是PHP中的浮点型同样是第一位表示符号,表示符号位之后的7位表示的是10的几次方这样的指数(所以浮点型才能表示更大位数,整型每一位都是有效数据),因此也就更不精确,想进行精确计算时千万不要使用浮点型
如下:
红色7位算的是10的指数,后面三个字节存储表示具体数值
00000000 00000000 00000000 00000000→11111111 11111111 11111111 11111111
C语言就支持有符号和无符号的两种整型,自然无符号的能表示的范围也就更大,
假设机器字长为8的话:
一个十进制的带符号整形 1,表达为二进制就是 (0000 0001)
一个十进制的带符号整形 -1,表达为二进制就是 (1000 0001)
那么,两者相加 ,用十进制运算 1+(-1)=0
在看二进制运算 (0000 0001)+(1000 0001)=(1000 0010) 这个数转换为十进制结果等于-2。
可以发现出问题了,如上所表示的方式,就是今天所要讲的原码。
数值X的原码记为[x]原,如果机器字长为n(即采用n个二进制位表示数据)。则最高位是符号位。0表示正号,1表示负号,其余的n-1位表示数值的绝对值。数值零的原码表示有两种形式:[+0]原=0000 0000 ,[-0]原=1000 0000.
例子:若机器字长n等于8,则
[+1]原=0000 0001 [-1]原=1000 0001
[+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 0001 [-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 0001 [-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 ) 10- ( 1 )10 = ( 1 )10 + ( -1 )10 = ( 0 )10
(00000001)原 + (10000001)原 = (10000010)原 = ( -2 ) 显然不正确.
通过上面原码计算式可以看出,当正数加上负数时,结果本应是正值,得到的却是负值(当然也有可能得到的是正数,因为被减数与减数相加数值超过0111 1111,即127,就会进位,从而进位使符号位加1变为0了,这时结果就是正的了)。而且数值部分还是被减数与减数的和。
并且,当负数加上负数时(这里就拿两个数值部分加起来不超过0111 1111的来说),我们可以明显看出符号位相加变为0,进位1被溢出。结果就是正数了。
因此原码的错误显而易见,是不能用在计算机中的。
既然原码并不能表示负数的运算问题,那么当然要另想他法了。这个方法就是补码,关于补码是如何提出的,我并不知道,但不得不说,这是一个最简洁的方法,当然,也可以用别的更复杂的方法,那就不是我们想要的了。
要谈补码,先看看补数的问题。什么是补数,举个简单的例子,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了。
此时补码变成1111 1111 ,反码:1111 1110,原码:1000 0001,即 -1
这样做的好处:
①、利用补数和溢出的原理,减法变成了加法
②、符号位不再是约束计算的问题,不会存在原码中的问题了,因为变成补码后,虽然最高位依然是1,但是这个1就不再是作为符号位了,而是作为一个普通的二进制位,参与运算了。
通过补数和溢出,解决了减法和负数问题。
十进制数求补码,补码求十进制数
十进制求补码:
如果是正数,直接求它的原码,符号位为0
如果是负数,比较好的方法是先求十六进制,在由十六进制求二进制,符号位为1,在除了符号位都取反,在加1,即可得到补码。
补码就十进制 :
根据符号位判断,如果符号位是0,表示是正数,就是原码,直接转换就十进制即可。
如果符号为是1,表示是负数。那么,连符号位在内都取反,在加1,将该二进制转换为十进制,该十进制数即使该负数的绝对值,加个负号-,就得到该负数。
本文转载自 蚂蚁辣舞 的CSDN 博客:
https://blog.csdn.net/changgui5211/article/details/46779441
改动一些小瑕疵的并加入了自己的理解和一些拓展