为什么浮点数不准确?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (178)

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

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

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

如此简单的数字怎么能“太大”而无法表达呢?64位记忆?

提问于
用户回答回答于

考虑分数2/3

在好的基础10中,我们通常把它写成类似的东西

  • 0.666
  • 0.666
  • 0.667

当我们看到这些表示时,我们倾向于将它们与分数2/3联系起来,即使只有第一个表示在数学上等于这个分数。第二次和第三次表示/近似的误差为0.001,这实际上比9.2到9.1999999999999993之间的误差严重得多。

数基

所以这里是数字基是关键的地方。如果我们试图在基数3中代表2/3,那么

(2/3)10=0.23

换句话说,我们有一个精确的,有限的表示相同的数目,通过切换基!虽然你可以把任何数字转换成任何基地,所有有理数在某些基中都有精确的有限表示,而在另一些基中则没有.

为了把这一点讲清楚,让我们来看看1/2。即使这个完全简单的数字在基数10和2中有一个精确的表示,但它需要在基3中重复表示,这可能会让您感到惊讶。

(1/2)10=0.510=0.12=0.1111...3

为什么浮点数不准确?

因为通常情况下,它们是近似于基数2(数字重复)不能有限表示的理性主义,而且通常它们是逼近实(可能是无理数)的数字,而这些数字可能不能用有限的数字来表示。任何基地。

用户回答回答于

一个非常简单的数字,比如说9.2,实际上这个分数是:

5179139571476070*2-49

其中指数是-49尾数是5179139571476070。原因是不可能代表一些十进制数就是指数和尾数都必须是整数。换句话说,所有浮动必须是整型乘以整数幂2.

9.2可能只是92/10,但是不能表示为2N如果n仅限于整数值。

看到数据

首先,以下几个函数构成32位和64位的组件。float,如果只关心输出(例如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)]

这个函数的背后有很多复杂的东西,这是非常切线的解释,但是如果感兴趣的话,对于我们的目的来说,重要的资源是结构模块。

Python的float是一个64位双精度的数字。在C、C++、Java和C#等其他语言中,双重精度有单独的类型。double,它通常被实现为64位。

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

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

解释数据

将看到,我已经将返回值拆分为三个组件。这些组成部分是:

  • 标志
  • 指数
  • 尾数(也称为符号和分数)

标志

符号作为一个位存储在第一个组件中。很容易解释:0表示浮点数为正数;1说明是阴性。因为9.2是正的,我们的符号值是0.

指数

指数存储在中间分量中,为11位。在我们的情况下,0b10000000010。以十进制表示的值。1026,这个组件的一个奇怪之处在于,必须减去一个等于2(# of bits) - 1 - 1要得到真正的指数,在我们的例子中,这意味着减去0b1111111111(十进制数)1023)要得到真正的指数,0b00000000011(十进制数3)。

尾数

尾数作为52位存储在第三个组件中。然而,这个组件也有一个怪癖。要理解这种怪癖,请考虑一个科学符号中的数字,如下所示:

6.0221413x1023

尾数将是6.0221413。回想一下,科学表示法中的尾数总是以一个非零的数字开始。二进制数也是如此,但二进制数只有两位数:01.所以二元尾数一开始1! When a float is stored, the 1在二进制尾数前面省略,以节省空间;我们必须将它放回第三个元素的前面,以获得千真万确尾数:

这不仅仅涉及一个简单的添加,因为存储在我们的第三个组件中的位实际上表示分数尾数的一部分。

在处理十进制数时,我们用乘积或除以10的幂来“移动小数点”。在二进制中,我们可以通过乘以或除以2的幂来做同样的事情。因为我们的第三个元素有52个位,所以我们除以它。二百五十二把它移到右边52个地方:

在十进制表示法中,这和除法一样675539944105574通过4503599627370496得到0.1499999999999999

现在,我们已经将第三个组件转换成一个小数,然后添加1给出真正的尾数。

重述组件

  • 签署(第一部分):0对于积极的人来说,1表示否定
  • 指数(中间分量):减去2(# of bits) - 1 - 1得到真正的指数
  • 尾数(最后一个组件):除以2(位数)加上1才能得到真正的尾数

计算数

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

.

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

1.1499999999999999 x 23(不准确!)

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

9.1999999999999993

表示为分数

九点二

在我们已经构建了这个数字,可以将它重构为一个简单的分数: 1.0010011001100110011001100110011001100110011001100110 x 1011 将尾数转换为整数: 10010011001100110011001100110011001100110011001100110 x 1011-110100 转换为十进制: 5179139571476070 x 23-52 减指数: 5179139571476070 x 2-49 将负指数转化为除法: 5179139571476070/249 乘法指数: 5179139571476070/562949953421312 等于: 9.1999999999999993

九点五

>>> 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

相等:

九点五

扫码关注云+社区