mhl*_*ter 181 language-agnostic floating-point precision
为什么有些数字在存储为浮点数时会失去准确性?
例如,十进制数9.2可以精确地表示为两个十进制整数(92/10)的比率,两者都可以用二进制(0b1011100/0b1010)精确表示.但是,存储为浮点数的相同比率永远不会完全等于9.2:
32-bit "single precision" float: 9.19999980926513671875
64-bit "double precision" float: 9.199999999999999289457264239899814128875732421875
Run Code Online (Sandbox Code Playgroud)
这样一个看似简单的数字如何在64位内存中表达"太大" ?
mhl*_*ter 219
在大多数编程语言中,浮点数的表示方式与科学记数法很相似:带有指数和尾数(也称为有效数字).比如9.2,一个非常简单的数字实际上是这个分数:
5179139571476070*2 -49
指数是-49和尾数的位置5179139571476070.不能用这种方式表示一些十进制数的原因是指数和尾数都必须是整数.换句话说,所有浮点数必须是整数乘以2的整数幂.
9.2可以简单地说92/10,但是如果n限于整数值,则10不能表示为2 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)]
Run Code Online (Sandbox Code Playgroud)
这个函数背后有很多复杂性,它解释起来很简单,但是如果你感兴趣的话,我们目的的重要资源就是struct模块.
Python float是一个64位的双精度数字.在其他语言中,例如C,C++,Java和C#,双精度具有单独的类型double,通常实现为64位.
当我们用我们的例子调用该函数时9.2,这是我们得到的:
>>> float_to_bin_parts(9.2)
['0', '10000000010', '0010011001100110011001100110011001100110011001100110']
Run Code Online (Sandbox Code Playgroud)
你会看到我把返回值分成三个部分.这些组件是:
符号作为单个位存储在第一个组件中.这很容易解释:0意味着浮动是一个正数; 1意味着它是消极的.因为9.2是正面的,我们的标志值是0.
指数作为11位存储在中间组件中.在我们的例子中0b10000000010.在十进制中,表示值1026.这个组件的一个怪癖是你必须减去一个等于2 (位数) - 1 - 1的数字来得到真正的指数; 在我们的例子中,这意味着减去 0b1111111111(十进制数1023)以获得真正的指数,0b00000000011(十进制数3).
尾数作为52位存储在第三个组件中.但是,这个组件也有一个怪癖.要理解这个怪癖,请考虑科学记数法中的数字,如下所示:
6.0221413x10 23
尾数将是6.0221413.回想一下,科学记数法中的尾数总是以一个非零数字开头.二进制也是如此,除了二进制只有两位数:0和1.所以二进制尾数始终以1!开头!存储浮点数时,1省略二进制尾数的前面以节省空间; 我们必须将它放回第三个元素的前面以获得真正的尾数:
1.0010011001100110011001100110011001100110011001100110
这不仅仅是一个简单的加法,因为存储在我们的第三个分量中的比特实际上代表了尾数的小数部分,即基数点的右边.
当处理十进制数时,我们通过乘以或除以10的幂来"移动小数点".在二进制中,我们可以通过乘以或除以2的幂来做同样的事情.由于我们的第三个元素有52位,我们除以通过2 52将它向右移动52个位置:
0.0010011001100110011001100110011001100110011001100110
在十进制,这是同分675539944105574的4503599627370496获得0.1499999999999999.(这是比率的一个示例,可以精确地以二进制表示,但仅大约以十进制表示;有关更多详细信息,请参阅:675539944105574/4503599627370496.)
现在我们已经将第三个组件转换为小数,添加1给出了真正的尾数.
0表示正数,1表示负数1以得到真正的尾数将所有三个部分放在一起,我们给出了这个二进制数:
1.0010011001100110011001100110011001100110011001100110 x 10 11
然后我们可以从二进制转换为十进制:
1.1499999999999999 x 2 3(不准确!)
并且9.2在存储为浮点值之后,乘以显示我们以()开头的数字的最终表示:
9.1999999999999993
现在我们已经建立了数字,可以将它重建为一个简单的部分:
1.0010011001100110011001100110011001100110011001100110 x 10 11
将尾数转换为整数:
10010011001100110011001100110011001100110011001100110 x 10 11-110100
转换为十进制:
5179139571476070 x 2 3-52
减去指数:
5179139571476070 x 2 -49
将负指数转换为除法:
5179139571476070/2 49
乘以指数:
5179139571476070/562949953421312
等于:
9.1999999999999993
>>> float_to_bin_parts(9.5)
['0', '10000000010', '0011000000000000000000000000000000000000000000000000']
Run Code Online (Sandbox Code Playgroud)
您已经可以看到尾数只有4位数后跟一大堆零.但是让我们来看看节奏.
组装二进制科学记数法:
1.0011 x 10 11
移动小数点:
10011 x 10 11-100
减去指数:
10011 x 10 -1
二进制到十进制:
19 x 2 -1
分数的负指数:
19/2 1
乘以指数:
19/2
等于:
9.5
Nic*_*rca 26
这不是一个完整的答案(mhlester已经涵盖了很多我不会复制的好地方),但我想强调一个数字的表示取决于你工作的基础.
在良好的基础10中,我们通常会将其写成类似的东西
当我们查看这些表示时,我们倾向于将它们中的每一个与分数2/3相关联,即使只有第一个表示在数学上等于分数.第二和第三表示/近似的误差大约为0.001,实际上比9.2和9.1999999999999993之间的误差差.事实上,第二个表示甚至没有正确舍入!然而,我们没有将0.666作为数字2/3的近似值的问题,所以我们不应该在大多数程序中如何逼近9.2.(是的,在某些程序中它很重要.)
所以这里的数字基础是重要的.如果我们试图在基数3中代表2/3,那么
(2/3)10 = 0.2 3
换句话说,通过切换基数,我们可以得到相同数字的精确有限表示!外卖是即使你可以将任何数字转换为任何基数,所有有理数在某些基础上都有精确的有限表示,但在其他基数中没有.
为了把这一点推到家里,让我们看看1/2.尽管这个完全简单的数字在基数10和2中具有精确表示,但它可能会让您感到惊讶,它需要在基数3中重复表示.
(1/2)10 = 0.5 10 = 0.1 2 = 0.1111 ... 3
因为它们经常是近似于在基数2中无法有限地表示的有理数(数字重复),并且通常它们近似于在任何基数中可能无法在有限多个数字中表示的实数(可能是无理数)数.
Lum*_*mpN 12
虽然所有其他答案都很好,但仍有一件事缺失:
这是不可能的代表无理数(如π, ,sqrt(2),log(3)等)精确!
这就是他们被称为非理性的原因.世界上没有多少比特存储就足以容纳其中一个.只有符号算术才能保持其精度.
虽然如果你将数学需求限制在有理数,但只有精度问题变得易于管理.您需要存储一对(可能非常大)整数a并b保存分数所代表的数字a/b.所有算术都必须在分数上完成,就像在高中数学中一样(例如a/b * c/d = ac/bd).
当然,你仍然会遇到同样的麻烦时pi,sqrt,log,sin,等都有涉及.
TL; DR
对于硬件加速算术,只能表示有限数量的有理数.每个不可表示的数字都是近似的.无论系统如何,都不能表示某些数字(即无理数).
有无限多个实数(多到你无法一一列举),也有无限多个有理数(可以一一列举)。
浮点表示是有限的(就像计算机中的任何东西一样),因此不可避免地无法表示很多很多数字。特别是,64 位只允许您区分 18,446,744,073,709,551,616 个不同的值(与无穷大相比,这不算什么)。按照标准约定,9.2 不是其中之一。对于某些整数 m 和 e,那些可以是 m.2^e 的形式。
您可能会想出不同的数字系统,例如基于 10 的数字系统,其中 9.2 将具有精确表示。但是其他数字,比如 1/3,仍然无法表示。
另请注意,双精度浮点数非常准确。它们可以用多达 15 位精确数字来表示非常广泛的范围内的任何数字。对于日常生活计算,4 或 5 位数字就足够了。你永远不会真正需要那 15 个,除非你想计算你生命中的每一毫秒。
| 归档时间: |
|
| 查看次数: |
27378 次 |
| 最近记录: |