语言不可知 – 为什么浮点数不准确?

为什么一些数字在作为浮点数存储时失去准确性?

例如,十进制数9.2可以精确地表示为两个十进制整数(92/10)的比率,二者都可以精确地表示为二进制(0b1011100 / 0b1010)。但是,存储为浮点数的相同比率不会完全等于9.2:

32-bit "single precision" float: 9.19999980926513671875
64-bit "double precision" float: 9.199999999999999289457264239899814128875732421875

这样一个明显简单的数字如何“过大”在64位内存中表达?

最佳答案
在大多数编程语言中,浮点数表示很多像scientific notation:用指数和尾数(也称为有效数)。一个非常简单的数字,说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的float是一个64位,双精度数字。在其他语言(如C,C,Java和C#)中,双精度具有单独类型double,通常实现为64位。

当我们用我们的例子9.2调用那个函数时,我们得到的是:

>>> float_to_bin_parts(9.2)
['0', '10000000010', '0010011001100110011001100110011001100110011001100110']

解释数据

你会看到我已经将返回值分成三个组件。这些组件是:

>标志
>指数
>尾数(也称为显着性或分数)

标志

符号作为单个比特存储在第一分量中。很容易解释:0表示float是一个正数; 1表示它是负数。因为9.2是正的,我们的符号值是0。

指数

指数作为11位存储在中间分量中。在我们的例子中,为0b10000000010。在十进制中,这表示值1026.此组件的一个奇怪的事情是,必须减去等于2(#的位) – 1 – 1的数,以获得真正的指数;在我们的例子中,这意味着减去0b1111111111(十进制数1023)得到真实指数0b00000000011(十进制数3)。

尾数

尾数作为52位存储在第三分量中。然而,这个组件也有一个怪癖。要理解这个怪癖,考虑一个科学记数法,如下:

6.0221413×1023

尾数将是6.0221413。回想一下,科学记数法中的尾数总是以单个非零数字开始。这同样适用于二进制,除了二进制只有两个数字:0和1.所以二进制尾数总是从1开始!当存储浮点时,省略二进制尾数前面的1以节省空间;我们必须将它放回到第三个元素的前面,以获得真正的尾数:

1.0010011001100110011001100110011001100110011001100110

这不仅仅涉及一个简单的加法,因为存储在第三个分量中的位实际上代表着尾数的分数部分,在radix point的右边。

当处理十进制数时,我们通过乘以10的幂来移动小数点。在二进制中,我们可以通过乘以2的幂来除法。因为我们的第三个元素有52位,它由252移动它向右52个地方:

0.0010011001100110011001100110011001100110011001100110

在十进制表示法中,这与将675539944105574除以4503599627370496得到0.1499999999999999相同。 (这是可以精确地以二进制表示的比率的一个示例,但是仅近似为十进制;关于更多细节,参见:675539944105574 / 4503599627370496.)

现在我们已经将第三个分量转换为分数,添加1给出真尾数。

重新组合组件

>符号(第一个分量):0表示正,1表示负
>指数(中间分量):减去2(位数) – 1 – 1以获得真实指数
>尾数(最后一个分量):除以2(位数),并加1得到真尾数

计算数字

把所有三个部分放在一起,我们得到这个二进制数:

1.0010011001100110011001100110011001100110011001100110 x 1011

然后我们可以将二进制转换为十进制:

1.1499999999999999 x 23 (inexact!)

并乘以显示在存储为浮点值之后,我们开始的数字(9.2)的最终表示:

9.1999999999999993

表示为分数

9.2

现在我们已经构建了数字,可以将它重建为一个简单的分数:

1.0010011001100110011001100110011001100110011001100110 x 1011

将尾数移到整数:

10010011001100110011001100110011001100110011001100110 x 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 x 10-1

二进制到十进制:

19 x 2-1

除数的负指数:

19 / 21

乘以指数:

19 / 2

等于:

9.5

进一步阅读

> The Floating-Point Guide: What Every Programmer Should Know About Floating-Point Arithmetic, or, Why don’t my numbers add up?(floating-point-gui.de)
> What Every Computer Scientist Should Know About Floating-Point Arithmetic(Goldberg 1991)
> IEEE Double-precision floating-point format(Wikipedia)
> Floating Point Arithmetic: Issues and Limitations(docs.python.org)
> Floating Point Binary

转载注明原文:语言不可知 – 为什么浮点数不准确? - 代码日志