Floating point 为什么浮点数不准确?

Floating point 为什么浮点数不准确?,floating-point,language-agnostic,precision,Floating Point,Language Agnostic,Precision,为什么有些数字在存储为浮点数时会失去准确性 例如,十进制数9.2可以精确地表示为两个十进制整数的比率(92/10),这两个整数都可以精确地表示为二进制(0b1011100/0b1010)。但是,作为浮点数存储的相同比率永远不会完全等于9.2: 32-bit "single precision" float: 9.19999980926513671875 64-bit "double precision" float: 9.1999999999999992894572642398998141288

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

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

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

这样一个看似简单的数字怎么可能“太大”而无法在64位的内存中表达呢?

在大多数编程语言中,浮点数的表示方式非常类似:用指数和尾数(也称为有效位)表示。一个非常简单的数字,比如说
9.2
,实际上是这个分数:

5179139571476070*2-49

其中指数为
-49
,尾数为
5179139571476070
。无法用这种方式表示某些十进制数的原因是,指数和尾数都必须是整数。换句话说,所有浮点必须是整数乘以2的整数幂

9.2
可能只是
92/10
,但如果n被限制为整数值,则10不能表示为2n


查看数据 首先,使用一些函数查看构成32位和64位
浮点的组件。如果您只关心输出(例如Python),请忽略这些内容:

def float_至_bin_部件(数字,位=64):
如果位==32:#单精度
int_pack='I'
浮动包装='f'
指数_位=8
尾数_位=23
指数偏差=127
elif位==64:#双精度。所有python浮动都是这样的
int_pack='Q'
浮动包装='d'
指数_位=11
尾数_位=52
指数偏差=1023
其他:
raise VALUERROR,“bits参数必须为32或64”
bin_iter=iter(bin(结构解包(int_pack,结构包(float_pack,number))[0])[2:][.rjust(位,'0'))
返回[''.join(islice(bin_iter,x))表示x in(1,指数位,尾数位)]
这个函数的背后有很多复杂的东西,解释起来很切题,但是如果你感兴趣的话,模块是我们的重要资源

Python的
float
是一个64位的双精度数字。在其他语言中,如C、C++、java和C语言,双精度有一个单独的类型:代码>双< /代码>,通常是64位。

当我们用示例调用该函数时,
9.2
,我们得到如下结果:

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

解读数据 您将看到我已将返回值拆分为三个组件。这些组成部分是:

  • 标志
  • 指数
  • 尾数(也称为有效位或分数)
签名 符号作为单个位存储在第一个组件中。很容易解释:
0
表示浮点为正数<代码>1
表示为负数。因为
9.2
为正,所以符号值为
0

指数

指数以11位存储在中间组件中。在我们的例子中,

0b10000000010
。以十进制表示,表示值
1026
。这个组件的一个奇怪之处是,必须减去一个等于2(#位)-1-1的数字才能得到真正的指数;在我们的例子中,这意味着减去
0b1111111111
(十进制数
1023
)得到真正的指数,
0b0000000011
(十进制数3)

尾数 尾数作为52位存储在第三分量中。然而,这个组件也有一个怪癖。为了理解这个怪癖,在科学符号中考虑一个数字,例如:

6.0221413x1023

尾数将是
6.0221413
。回想一下,科学记数法中的尾数总是以一个非零的数字开始。二进制也是如此,只是二进制只有两个数字:
0
1
。所以二进制尾数总是以
1
开头!存储浮点时,二进制尾数前面的
1
被省略以节省空间;我们必须把它放回第三个元素的前面,才能得到真正的尾数:

1.0010011001100110

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

在处理十进制数时,我们通过乘以或除以10的幂来“移动小数点”。在二进制中,我们可以通过乘以或除以2的幂来做同样的事情。由于第三个元素有52位,我们将其除以252,将其向右移动52位:

0.0010011001100110


在十进制表示法中,这与将
675539944105574
除以
4503599627370496
得到
0.1499999999999999
相同。(这是一个可以精确表示为二进制,但只能近似表示为十进制的比率示例;有关更多详细信息,请参阅:)

现在我们已经将第三个分量转换为一个小数,加上
1
就得到了真正的尾数

重述组件
  • 符号(第一个分量):
    0表示正,
    1表示负
  • 指数(中间分量):减去2(#位)-1-1得到真正的指数
  • 尾数(最后一部分):除以2(#位),然后加
    1
    ,得到真正的尾数

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

1.0010011001100110 x 1011

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

>>> float_to_bin_parts(9.5)
['0', '10000000010', '0011000000000000000000000000000000000000000000000000']