Why Int And Float Have The Same Size Of Bits But Have Different Ranges

对于C与其它大多数语言中,int和float都是32 bit。但int可表示的范围是 -2147483648 至 2147483647 ,而float的表示范围却大了很多,大约是:-10^38 至 10^38。

为什么?

理解这个问题的关键是这样一个事实:计算机只认识1和0两个状态。不管int还是float,甚至声音、视频,对计算机来说都是一系列的01组合。

先来看下int类型。数字1到10在内存中的bit依次是这样的

0001
0010
0011
0100
0101
0110
0111
1000
1001
1010

上面用到了四个bit,可表示的数字范围是 (0 - 15 / 0000 - 1111) 共16个数字。如果用这四位来表示有符号的整数,那可表示的数字是这样的

1000  -8
1001  -7
1010  -6
1011  -5
1100  -4
1101  -3
1110  -2
1111  -1
0000  0
0001  1
0010  2
0011  3
0100  4
0101  5
0110  6
0111  7

从 -8 到 7,表示范围也是16 (2^4) 个数字,依此推广到32bit时,表示范围就上升为 2^32 了,即 -2147483648 到 2147483647

下面说float类型(单精度浮点数)

float类型中由于有小数点的存在不能简单地像int那样 (e.g. 数字5表示为 0101) 表示数字。那如何用 0, 1 来表示 13.625 这个数值呢?

比较直接的方法可能是这样:首先我们可以将13表示为二进制 1101,其次我们可以将625表示为 1001110001,这样我们就可以将13.625 表示为 11011001110001(这里我们不得不规定好:前四位bit是整数部分,后面的bit是小数部分)

我们用这个方法表示 13.6250000123 就成这了这个样子:

1101101110100100001110110111011111011

(共37bits,前四位bit是整数部分,后面的bit是小数部分)

这个表示方法有两个主要缺点:
1. 总位数不统一:同样是13点多的小数,一个用14bits,一个却是37bits
2. 小数点的界定位置不固定

那float到底是如何存储的呢?

回头看 13.625 这个数值,小数部分0.625 (625/1000) 可以写成 0.5 + 0.125,即 1/2 + 1/8,也即 1x2^(-1) + 0x2^(-2) + 1x2^(-3)。所以13.625 就可以写成这样: 1101101。

1101.101 (二进制) = 1x2^3 + 1x2^2 + 0x2^1 + 1x2^0 + 1x2^(-1) + 0x2^(-2) + 1x2^(-3) = 13.625 (十进制)

这个方法已经开始接近现实中浮点数的表示方式了。

现代计算机差不多都是基于 IEEE 754 标准来存储浮点数。对于单精度的浮点数来说,内存中的32bit是这个样子:

S EEEEEEEE MMMMMMMMMMMMMMMMMMMMMMM

S表示符号Sign,8bit来表示幂Exponent,23bit来表示系数 (Mantissa, coefficient, significand)

数值计算方法是: (−1)s × c × b^q

这里b是对于计算机来说为2。q为2的幂,执照规则是 EEEEEEEE(二进制) 减去 127(十进制) 所得的数。

c是系数。类似于科学计数法 1.23 x 10^n 中的系数,c总是以一个1开头,比如 1.101 (对应十进制中的1.625)。但这里有一个小小的花招:因为总是有一个1开头,那我们就不用浪费一个bit来存储它了。所以这个23bit的系数是省略了开头的1以后的结果。比如23bit的系数是 01010000000000000000000 我们计算c时,应该用 101010000000000000000000 来计算。可以说这个花招提高了浮点数一个bit的精度。

这时我们就可以算出float的大致范围来了。因为幂这部分是个8bit的二进制数,范围是 0 - 255,再减去 127 这个基准数字后幂的实际范围是 -127 到 128,而 log10(2^128) = 38.53184。由此得知单精度浮点数的大致范围是 -10^38 到 +10^38。

现在来用这种方式表示13.625。我们已经知道它的二进制形式为1101.101,而IEEE 754要求 (1.XXXX) x 2^e 的形式,所以1101.101要表示为 (1.101101) x 2^3。这里幂就是3 + 127 = 130 ,二进制为 10000010。系数部分省略开头的1后为 101101。符号为正,所以S这个bit值为0。最终13.625在内存中是这样子的:

0 10000010 10110100000000000000000

最后来考虑下 0.1 这个数字
当我们尝试写成二进制时,得到下面的表示,bit数还可以更多,但系数中我们只能放下23位。

0.000110011001100110011001100110011

改为 (1.XXXX) x 2^e 的形式并四舍五入后为 1.10011001100110011001101 x 2^(-4),这里幂为 -4 + 127 = 123 故 0.1 在内存中的表示为

0 01111011 10011001100110011001101

很明显我们计算 0.1 时,并不能像 0.625 那样很整齐地得到 0.101, 即 1x2^(-1) + 0x2^(-2) + 0x2^(-3) 。所以浮点数中并不能确切地表示 0.1 这个数值,只能取到一点精度。这也就是在Python中执行 a = 0.1; a 得到结果是 0.10000000000000001 (注意这里Python用的是双精度) 而不是 0.1的原因。也就是说float可表示数值范围远超过int类型一部分凭借是精度上的损失。

参考:
http://en.wikipedia.org/wiki/Floating_point
http://en.wikipedia.org/wiki/Single_precision_floating-point_format