提问人: 提问时间:2/20/2014 最后编辑:9 revs, 6 users 68%mhlester 更新时间:6/27/2023 访问量:80296
为什么浮点数不准确?
Why are floating point numbers inaccurate?
问:
为什么有些数字在存储为浮点数时会失去准确性?
例如,十进制数可以精确地表示为两个十进制整数()的比率,两者都可以精确地用二进制()表示。但是,存储为浮点数的相同比率永远不会完全等于: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
其中指数是,尾数是 。不可能以这种方式表示某些十进制数的原因是指数和尾数都必须是整数。换句话说,所有浮点数都必须是整数乘以 2 的整数幂。-49
5179139571476070
9.2
可以简单地表示 ,但如果 n 限制为整数值,则 10 不能表示为 2n。92/10
查看数据
首先,通过几个函数来查看构成 32 位和 64 位的组件。如果你只关心输出(Python 中的示例),请忽略这些内容:float
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 是一个 64 位的双精度数字。在其他语言如C,C++,Java和C#中,双精度有一个单独的类型,通常实现为64位。float
double
当我们用我们的示例调用该函数时,我们得到的是:9.2
>>> float_to_bin_parts(9.2)
['0', '10000000010', '0010011001100110011001100110011001100110011001100110']
解释数据
您将看到我已将返回值拆分为三个组件。这些组件包括:
- 标志
- 指数
- 尾数(也称为 Significand 或分数)
标志
符号作为单个位存储在第一个组件中。这很容易解释:表示浮点数是正数; 意味着它是负面的。因为是正数,所以我们的符号值是 。0
1
9.2
0
指数
指数以 11 位的形式存储在中间分量中。在我们的例子中,.在十进制中,表示值 。这个组件的一个怪癖是,你必须减去一个等于 2(# 位) - 1 - 1 的数字才能得到真正的指数;在我们的例子中,这意味着减去(十进制数)以获得真正的指数(十进制数 3)。0b10000000010
1026
0b1111111111
1023
0b00000000011
尾数
尾数以 52 位的形式存储在第三个组件中。但是,这个组件也有一个怪癖。要理解这个怪癖,请考虑科学记数法中的数字,如下所示:
6.0221413x1023
尾数将是.回想一下,科学记数法中的尾数总是以一个非零数字开头。二进制也是如此,只是二进制只有两位数:和 .所以二进制尾数总是以 !当存储浮点数时,省略二进制尾数前面的尾数以节省空间;我们必须将它放回第三个元素的前面才能得到真正的尾数:6.0221413
0
1
1
1
1.0010011001100110011001100110011001100110011001100110
这不仅仅是一个简单的加法,因为存储在第三个组件中的位实际上代表了尾数的小数部分,位于基点的右侧。
在处理十进制数时,我们通过乘以或除以 10 的幂来“移动小数点”。在二进制中,我们可以通过乘以或除以 2 的幂来做同样的事情。由于我们的第三个元素有 52 位,我们将其除以 2 52 以将其向右移动 52 位:
0.0010011001100110011001100110011001100110011001100110
在十进制表示法中,这与除以得到 .(这是一个比率的例子,可以精确地用十进制表示,但只能近似地用二进制表示;有关详细信息,请参阅:675539944105574 / 4503599627370496。675539944105574
4503599627370496
0.1499999999999999
现在我们已经将第三个分量转换为小数,加法得到真正的尾数。1
回顾组件
- 符号(第一部分):表示正数,表示负数
0
1
- 指数(中间分量):减去 2(# 位) - 1 - 1 得到真正的指数
- 尾数(最后一个部分):除以 2(# 位)并相加得到真正的尾数
1
计算数字
将所有三个部分放在一起,我们得到这个二进制数:
1.0010011001100110011001100110011001100110011001100110 x 1011
然后我们可以将其从二进制转换为十进制:
1.14999999999999999 x 23 (不准确!
并相乘以显示我们以 () 开头的数字在存储为浮点值后的最终表示: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
延伸阅读
- 浮点指南:每个程序员都应该了解的浮点运算知识,或者,为什么我的数字不加起来?(floating-point-gui.de)
- 每个计算机科学家都应该知道的浮点运算(Goldberg 1991)
- IEEE 双精度浮点格式 (Wikipedia)
- 浮点运算:问题和限制 (docs.python.org)
- 浮点二进制
评论
这不是一个完整的答案(mhlester 已经涵盖了很多我不会重复的好地方),但我想强调一个数字的表示在多大程度上取决于你正在工作的基础。
考虑分数 2/3
在 good-ol' base 10 中,我们通常将其写成类似
- 0.666...
- 0.666
- 0.667
当我们查看这些表示时,我们倾向于将它们中的每一个都与分数 2/3 相关联,即使只有第一个表示在数学上等于分数。第二和第三种表示/近似值的误差约为 0.001,这实际上比 9.2 和 9.1999999999999993 之间的误差要差得多。事实上,第二个表示甚至没有正确舍入!尽管如此,我们对 0.666 作为数字 2/3 的近似值没有问题,因此我们对 9.2 在大多数程序中的近似方式应该没有问题。(是的,在某些程序中,这很重要。
数基数
因此,这就是数字基数至关重要的地方。如果我们试图以 2/3 为基数表示 3,那么
(2/3)10 = 0.23
换句话说,通过切换基数,我们得到了相同数字的精确、有限的表示!结论是,即使您可以将任何数转换为任何基数,所有有理数在某些基数中都具有精确的有限表示,但在其他基数中则没有。
为了说明这一点,让我们看看 1/2。您可能会感到惊讶,尽管这个非常简单的数字在基数 10 和 2 中具有精确的表示,但它需要在基数 3 中重复表示。
(1/2)10 = 0.5 10 = 0.12 =0.1111...3
为什么浮点数不准确?
因为很多时候,它们是近似于不能用以 2 为基数(数字重复)有限表示的有理数,而且通常它们是近似于实数(可能是无理数)数,而这些数可能无法在任何基数中以有限数量的数字表示。
评论
1/3
1/10
N
π
虽然所有其他答案都很好,但仍然缺少一件事:
精确地表示无理数(例如 π、sqrt(2)
、log(3)
等)是不可能的!
这实际上就是为什么它们被称为非理性的原因。世界上再多的比特存储也不足以容纳其中之一。只有符号算术才能保持它们的精确性。
尽管如果你将你的数学需求限制在有理数上,那么只有精确度问题变得可控。您需要存储一对(可能非常大)整数并保存由分数表示的数字。你所有的算术都必须在分数上完成,就像在高中数学中一样(例如)。a
b
a/b
a/b * c/d = ac/bd
但是,当然,当涉及 、、 、 等时,您仍然会遇到同样的麻烦。pi
sqrt
log
sin
TL;博士
对于硬件加速算术,只能表示有限数量的有理数。每个不可表示的数字都是近似值。无论系统如何,某些数字(即无理数)永远无法表示。
评论
为什么我们不能用二进制浮点数表示 9.2?
浮点数是一种位置编号系统(略微简化),具有有限的位数和可移动的基数。
如果分母的质因数(当分数以最低项表示时)是基数的因数,则分数只能在位置编号系统中使用有限数量的位数精确表示。
10 的质因数是 5 和 2,因此在以 10 为基数的中,我们可以表示形式 a/(2b5c) 的任何部分。
另一方面,2 的唯一质因数是 2,因此在以 2 为基数的情况下,我们只能表示 a/(2b 形式的分数)
为什么计算机使用这种表示?
因为它是一种简单的格式,并且对于大多数用途来说都足够准确。基本上,科学家使用“科学记数法”并在每一步将他们的结果四舍五入到合理的位数的原因相同。
当然可以定义分数格式,例如,使用32位分子和32位分母。它将能够表示IEEE双精度浮点数无法表示的数字,但同样,将有许多数字可以用双精度浮点数表示,而这些数字无法以这种固定大小的分数格式表示。
然而,最大的问题是,这样的格式进行计算是一件痛苦的事情。有两个原因。
- 如果你想让每个数字只有一个表示形式,那么在每次计算之后,你需要将分数减少到最低项。这意味着对于每个操作,您基本上都需要进行最大公约数计算。
- 如果经过计算,您最终会得到一个无法表示的结果,因为分子或分母您需要找到最接近的可表示结果。这绝非易事。
有些语言确实提供了分数类型,但通常它们会与任意精度相结合,这样就避免了担心近似分数的问题,但它会产生自己的问题,当一个数字经过大量的计算步骤时,分母的大小和分数所需的存储可能会爆炸。
一些语言还提供十进制浮点类型,这些类型主要用于计算机获得的结果与预先存在的舍入规则相匹配的情况,这些规则是为人类编写的(主要是财务计算)。这些比二进制浮点运算稍微难用一些,但最大的问题是大多数计算机不为它们提供硬件支持。
有无限多的实数(太多了,你无法枚举它们),还有无限多的有理数(可以枚举它们)。
浮点表示是有限的(就像计算机中的任何东西一样),因此不可避免地无法表示许多许多数字。特别是,64 位只允许您区分 18,446,744,073,709,551,616 个不同的值(与无穷大相比,这算不了什么)。按照标准约定,9.2 不是其中之一。对于某些整数 m 和 e,可以采用 m.2^e 的形式。
你可能会想出一个不同的数字系统,例如基于10,其中9.2将有一个精确的表示。但其他数字,比如 1/3,仍然无法表示。
另请注意,双精度浮点数非常准确。它们可以表示非常宽范围内的任何数字,最多有 15 个精确数字。对于日常生活计算,4 或 5 位数字绰绰有余。你永远不会真正需要这 15 秒,除非你想计算你一生中的每一毫秒。
评论
9.2
在 64 位内存中表示并不是“太大”,它只是不完全等于 binary64 值允许的 1022*2^52 = 4602678819172646912 个预定义值中的任何一个,因此它被舍入到最接近的值。