gpt4 book ai didi

floating-point - 为什么 float 不正确?

转载 作者:行者123 更新时间:2023-12-01 18:16:39 31 4
gpt4 key购买 nike

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

例如,十进制数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位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)]


该函数背后有很多复杂性,并且很容易解释,但是如果您感兴趣的话, struct模块对我们而言是重要的资源。

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(位数)-1-1的数字才能得到真实的指数。在我们的例子中,这意味着减去 0b1111111111(十进制数 1023)以获得真实指数 0b00000000011(十进制数3)。

尾数

尾数作为52位存储在第三部分中。但是,此组件也有一个怪癖。要理解这一怪异现象,请考虑用科学计数法表示的数字,如下所示:


6.0221413x1023


尾数为 6.0221413。回想一下,科学计数法中的尾数始终以单个非零数字开头。二进制也是如此,只不过二进制只有两位数字: 01。因此二进制尾数始终以 1开头!当存储浮点数时,将省略二进制尾数前面的 1以节省空间。我们必须将其放回第三个元素的前面以获取真实的尾数:


1.0010011001100110011001100110011001100110011001100110110


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

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


0.0010011001100110011001100110011001100110011001100110110


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

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

重新盖上组件


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




计算数字

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


1.0010011001100110011001100110011001100110011001100110 x 1011


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


1.1499999999999999 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






进一步阅读


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(维基百科)
Floating Point Arithmetic: Issues and Limitations(docs.python.org)
Floating Point Binary

关于floating-point - 为什么 float 不正确?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60350933/

31 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com