在大多数编程语言中,浮点数的表示方式很像科学记数法:用一个指数和一个尾数(也称为显号)表示。一个非常简单的数字,比如9.2,实际上是这个分数:
5179139571476070 * 2-49
其中指数为-49,尾数为5179139571476070。不能用这种方式表示一些十进制数的原因是指数和尾数都必须是整数。换句话说,所有浮点数必须是整数乘以2的整数幂。
9.2可能只是92/10,但如果n被限制为整数值,10就不能表示为2n。
查看数据
首先,使用几个函数来查看构成32位和64位浮点数的组件。如果你只关心输出,就忽略这些(Python中的例子):
def float_to_bin_parts(number, bits=64):
if bits == 32: # single precision
int_pack = 'I'
float_pack = 'f'
exponent_bits = 8
mantissa_bits = 23
exponent_bias = 127
elif bits == 64: # double precision. all python floats are this
int_pack = 'Q'
float_pack = 'd'
exponent_bits = 11
mantissa_bits = 52
exponent_bias = 1023
else:
raise ValueError, 'bits argument must be 32 or 64'
bin_iter = iter(bin(struct.unpack(int_pack, struct.pack(float_pack, number))[0])[2:].rjust(bits, '0'))
return [''.join(islice(bin_iter, x)) for x in (1, exponent_bits, mantissa_bits)]
这个函数背后有很多复杂的东西,不需要解释,但如果您感兴趣,对于我们的目的来说,重要的资源是struct模块。
Python的浮点数是一个64位的双精度数。在其他语言中,如C、c++、Java和c#, double-precision有一个单独的类型double,通常实现为64位。
当我们在9.2的例子中调用这个函数时,得到的结果如下:
>>> float_to_bin_parts(9.2)
['0', '10000000010', '0010011001100110011001100110011001100110011001100110']
解读数据
您将看到我将返回值分为三个组件。这些组件是:
标志
指数
尾数(也称为显数或分数)
Sign
符号作为单个位存储在第一个组件中。这很容易解释:0表示浮点数是正数;1表示是负的。因为9.2是正数,所以符号值是0。
指数
指数以11位的形式存储在中间的组件中。在我们的例子中,是0b10000000010。在十进制中,它表示值1026。这个组件的一个奇怪之处在于,你必须减去一个等于2(# of bits) - 1 - 1的数字才能得到真正的指数;在我们的例子中,这意味着减去0b1111111111(十进制数1023)来得到真正的指数0b00000000011(十进制数3)。
尾数
尾数以52位的形式存储在第三个分量中。然而,这个组件也有一个奇怪的地方。为了理解这个怪癖,考虑一个科学计数法中的数字,像这样:
6.0221413 x1023
尾数是6.0221413。回想一下,科学记数法中的尾数总是以单个非零数字开头。这同样适用于二进制,除了二进制只有两个数字:0和1。所以二进制尾数总是以1开头!当存储浮点数时,二进制尾数前面的1被省略以节省空间;我们必须把它放到第三个元素的前面,才能得到真正的尾数:
1.0010011001100110011001100110011001100110011001100110
这不仅仅是一个简单的加法,因为存储在第三个分量中的位实际上表示尾数的小数部分,在基数点的右边。
当处理十进制数字时,我们通过乘以或除以10的幂来“移动小数点”。在二进制中,我们可以通过乘以或除以2的幂来做同样的事情。由于第三个元素有52位,我们将它除以252,向右移动52位:
0.0010011001100110011001100110011001100110011001100110
在十进制计数法中,这相当于用675539944105574除以4503599627370496得到0.14999999999999999999。(这是一个可以用二进制精确表示,但只能用十进制近似表示的比率的例子;详细信息请参见:675539944105574 / 4503599627370496。)
现在我们已经将第三个分量转换为小数,加1得到真正的尾数。
重述组件
符号(第一个分量):0表示正,1表示负
指数(中间分量):减去2(# of bits) - 1 - 1得到真正的指数
尾数(最后一个分量):除以2(# of bits)再加1得到真正的尾数
计算数字
把这三部分放在一起,我们得到这个二进制数:
1.0010011001100110011001100110011001100110011001100110 x 1011
然后我们可以把它从二进制转换成十进制:
1.1499999999999999 x 23(不准确!)
然后相乘,以显示我们开始的数字(9.2)被存储为浮点值后的最终表示形式:
9.1999999999999993
用分数表示
9.2
现在我们已经建立了这个数字,可以将它重构为一个简单的分数:
1.0010011001100110011001100110011001100110011001100110 x 1011
将尾数移到整数:
10010011001100110011001100110011001100110011001100110 × 1011-110100
转换为十进制:
5179139571476070 x 23-52
减去指数:
5179139571476070 x 2-49
将负指数化为除法:
5179139571476070/249
用指数:
5179139571476070/562949953421312
等于:
9.1999999999999993
9.5
>>> float_to_bin_parts(9.5)
['0', '10000000010', '0011000000000000000000000000000000000000000000000000']
你已经可以看到尾数只有4位数字,后面跟着一大堆零。我们来看看这个步骤。
汇编二进制科学记数法:
1.0011 x 1011
小数点移位:
10011 x 1011-100
减去指数:
10011 × 10-1
二进制到十进制:
19 x 2-1
除法的负指数:
19/21
用指数:
19/2
等于:
9.5
进一步的阅读
浮点指南:每个程序员都应该知道的浮点算术,或者,为什么我的数字加不起来?(floating-point-gui.de)
关于浮点运算,每个计算机科学家都应该知道什么(Goldberg 1991)
IEEE双精度浮点格式(维基百科)
浮点运算:问题和限制(docs.python.org)
浮点二进制