一、整数在计算机中的表示
在C/C++中,整数一般分为无符号数(unsigned char、unsigned short、unsigned int等)和有符号数(char、short、int、long等),在计算机中通过补码来表示,那么有童鞋会问了,不是有那什么原码、反码之类的吗?为什么不用它们而偏偏用补码呢?
一开始我也有这样的困惑,于是通过各种查,各种看,算是理解了一点点,这里不打算详细解释原码和反码,只举几个例子说明在计算机中为什么不用它们表示十进制数,而用补码来表示。
我们能认识0、1、2、3、、、9等自然数,然而计算机却不认识,计算机“笨”的只认识0和1,所以那些大神们就通过各种手段,将现实世界中的各种信息都转化为一连串由0和1组成的序列。然后计算机就通过不同的解读方式,对这一系列的01串串根据具体的上下文解释成不同的信息。
我们都知道1 + (-1) = 0
,那么如果用原码、反码来表示这个会怎样呢?
原码:
原码 十进制
0000 0001 1
+ 1000 0001 -1
----------------
1000 0010 -2
反码:
反码 十进制
0000 0001 1
+ 1111 1110 -1
----------------
1111 1111 -0 // 需要通过转换为原码,才能直观的看出所表示的实际数字,原码:1000 0000
通过上面两个例子,我们看到,不管是用原码还是反码,都不能正确的表达结果。另外,我们可以发现,在原码和反码中,对于0的表示有两种,一种是0000 0000
(+0),一种是1000 0000
(-0),这也是一种缺陷,最好的方式是在给定一个十进制整数的范围内,该范围内的每一个数在相应的二进制表示中有且只有一个与之对应,也就是存在一一映射的关系。那么如何解决上述的问题呢?
终于等到补码登场了,一般主角都压轴,哈哈。补码,就是在反码的基础上再加1,比如-1的补码就是1111 1110
+ 1
= 1111 1111
,用补码来进行上面的计算就是:
反码 十进制
0000 0001 1
+ 1111 1111 -1
----------------
1 0000 0000 0 // 最高位的1被丢弃
结果为0,符合我们的实际逻辑。我们再来看看前面提到的一一映射的问题,在C/C++中,char
表示一个8bit的有符号整数,在计算机中用补码表示,所能表示的十进制范围为[-128, 127]
共256个数,那么这个范围是怎么得来的呢?
补码 十进制
0000 0000 0
0000 0001 1
...
...
...
0111 1111 127(2^7 - 1)
1000 0000 -128(-2^7 + 0)
1000 0001 -127(-2^7 + 1)
...
...
...
1111 1111 -1(-2^7 + 127)
从上面可以看出,前128个数表示正数(最高位以0开始),后128个表示负数(最高位以1开始)。对于unsigned char
来说,由于其为无符号数,所以所能表示的最大范围为[0, 255]
。对于其它的整数数据类型可以通过类似的方法得到他们的表示范围,感兴趣的童鞋可以自己算算试试。
二、小数在计算机中的表示
对于float
来说,在一般机器上都是以四个字节即32bit来表示,那么它在计算机中是怎么表示的呢?
IEEE 754规定,对于32位的浮点数,最高位的那位表示符号位s
,紧接着的8位是指数E
,剩下的23位为有效数字M
或者称为尾数。
在十进制中,对于任何一个小数都可以用科学计数法来表示,比如123.567
的科学计数法就是1.23456*10^2
,0.0097
就是9.7*10^-3
;同样地,在二进制的世界中,任何一个小数也可以用类似的科学技术法来表示,只不过不在以10
为底,而是以2
为底。那么对于十进制的123.567
和0.0097
在二进制中如何表示呢?
十进制小数:123.567
在二进制中的表示:
1. 首先取整数部分123,用二进制中表示为1111011。
2. 小数部分.567如何表示?
我们都知道,在十进制整数进行二进制转化的时候有一个方法,就是对十进制数进行不断的除以2,然后取余数(要么为0,要么为1),然后按逆序排列就得到了十进制整数的二进制表示。
那么对于十进制的小数该如何用二进制表示是否找到了灵感?对,就是对小数部分不断的乘以2,然后对得到的新小数取整数部分(要么为0,要么为1)直到小数部分为0或者已经达到精度上限(比如所取得0、1已经达到23位了,再继续乘下去,对于32位的float来说,已经不能继续存储了)。
0.567 * 2 = 1.134 …… 取1 , 基数 = 0.134
0.134 * 2 = 0.268 …… 取0 , 基数 = 0.268
0.268 * 2 = 0.563 …… 取0 , 基数 = 0.563
0.563 * 2 = 1.126 …… 取1 , 基数 = 0.126
……
……
……
最终结果为:10010001001001101110100。(23位)
所以.567在二进制中的表示为:.10010001001001101110100。
因而123.567在二进制中的表示为:1111011.10010001001001101110100。
用科学计数法表示为:1.1110 1110 0100 0100 1001 1011 1010 0 * 2^6。
通过IEEE 754的规定,我们可以很容易将其在计算机中表示:
符号位:0
指数位:Exp - 127 = 6 --> Exp = 133 -->1000 0101
尾数部分:1110 1110 0100 0100 1001 101
【IEEE 754规定,在计算机内部保存M时(1.XXXXXXX),默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。】
所以123.567在计算机中的完整表示就是:
0 1000 0101 1110 1110 0100 0100 1001 101
通过在线工具,可以验证上面结果的正确性,如下图:
图3 123.567在计算机中的表示对于0.0097
也可以用同样的方法得到其在计算机内部的表示,这里就不在赘述了,有兴趣的童鞋可以自己尝试一下,然后通过上面提到的在线工具验证一下。
这里稍微再提一下,在C/C++中,float
所能保持的精度一般为小数位6-7位,那么这个6或7是怎么得来的呢?通过上面我们知道,对于32位的浮点数,后23位表示的是小数部分,总共可以表示2^23 = 8388608
,也就是说,后面的23位最多可以表示十进制小数的前7位小数位,通过四舍五入,至少可以保证6位的精度是正确的,至于第7位......呵呵!
好了,通过上面的描述,基本了解了float
在计算机内存是如何表示的了,那么double
在计算机中如何表示呢?其实和float
的表示原理是一样的,只是double
一般用64位bit来表示,最高位还是符号位,但是指数部分用了11位来表示,而剩余的52位全用来表示尾数(所能表示的精度一般为2^52 = ?
15-16位),其余的规则和32位的float
没啥区别,所以这里就仅放一张图,各位童鞋可以慢慢去体会。
后记
这是我在简书上发表的第一篇文章,希望自己能一直坚持下去,养成一个习惯。
对于文章中的不足之处,恳请各位读者批评指正。
简书给我的感觉就是三个字 -- 简洁、美。
感谢这个平台。
网友评论