您的位置:首页 > 理论基础

C/C++中的整数和浮点数在计算机中是如何表示的?

2017-10-13 16:03 417 查看

一、整数在计算机中的表示

在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
或者称为尾数。

图1 32位浮点数在计算机中的表示

图2 个人理解

在十进制中,对于任何一个小数都可以用科学计数法来表示,比如
12
a692
3.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
没啥区别,所以这里就仅放一张图,各位童鞋可以慢慢去体会。

图4 64位浮点数在计算机中的表示

参考

你应该知道的浮点数基础知识
浮点数的二进制表示
IEEE 754-1985

(完)

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: