考虑以下代码:

0.1 + 0.2 == 0.3  ->  false
0.1 + 0.2         ->  0.30000000000000004

为什么会出现这些错误?


当前回答

由于这篇文章对当前的浮点实现进行了一般性的讨论,我想补充一下,有一些项目正在解决它们的问题。

看看https://posithub.org/例如,它展示了一种称为posit(及其前身unum)的数字类型,它承诺以更少的比特提供更好的精度。如果我的理解是正确的,它也解决了问题中的问题。非常有趣的项目,背后的人是数学家约翰·古斯塔夫森博士。整个过程都是开源的,用C/C++、Python、Julia和C#实现了许多实际的实现(https://hastlayer.com/arithmetics).

其他回答

我可以补充一下吗;人们总是认为这是一个计算机问题,但如果你用手(以10为基数)计算,你就不能得到(1/3+1/3=2/3)=真,除非你有无穷大可以将0.333…加到0.333……就像(1/10+2/10)一样==基数2的3/10问题,您将其截断为0.333+0.333=0.666,并可能将其舍入为0.667,这在技术上也是不准确的。

用三进制数,三分之三不是问题——也许有人会问为什么你的十进制数学被打破了。。。

它被打破的方式与你在小学学习并每天使用的十进制(以10为基础)表示法完全相同,只是以2为基础。

要理解,请考虑将1/3表示为十进制值。这是不可能做到的!世界将在你写完小数点后的3之前结束,所以我们写了一些地方,认为它足够准确。

以同样的方式,1/10(十进制0.1)不能以2为基数(二进制)精确地表示为“十进制”值;小数点后的重复模式将永远持续下去。该值不精确,因此无法使用常规浮点方法对其进行精确计算。与基数10一样,还有其他值也显示了这个问题。

不,不破,但大多数小数必须近似

总结

浮点运算是精确的,不幸的是,它与我们通常的以10为基数的数字表示法不太匹配,所以我们经常给它的输入与我们写的略有不同。

即使是像0.01、0.02、0.03、0.04…0.24这样的简单数字也不能精确地表示为二进制分数。如果你数到0.01、.02、.03…,直到你数到0.25,你才能得到以2为底的第一个分数。如果你尝试使用FP,那么你的0.01会稍微有点偏差,所以要将其中的25个相加到一个精确的0.25,就需要一长串的因果关系,包括保护位和舍入。很难预测,所以我们举手说“FP不准确”,但事实并非如此。

我们不断地给FP硬件一些在基数10中看似简单但在基数2中却是重复的分数。

这是怎么发生的?

当我们用十进制书写时,每个分数(特别是每个终止的小数)都是形式的有理数

          a/(2n x 5m)

在二进制中,我们只得到2n项,即:

a/2n

所以在十进制中,我们不能表示1/3。因为基数10包括2作为素因子,所以我们可以写成二进制分数的每个数字也可以写成基数10的分数。然而,我们写为10进制分数的任何东西都很难用二进制表示。在0.01、0.02、0.03…0.99的范围内,只有三个数字可以用我们的FP格式表示:0.25、0.50和0.75,因为它们是1/4、1/2和3/4,所有的数字都只使用2n项。

在base10中,我们不能表示1/3。但在二进制中,我们不能做1/10或1/3。

因此,虽然每一个二进制分数都可以用十进制来表示,但反过来却不正确。事实上,大多数小数在二进制中重复。

处理它

开发人员通常被要求进行<epsilon比较,更好的建议可能是舍入为整数值(在C库中:round()和round f(),即保持FP格式),然后进行比较。舍入到特定的小数部分长度可以解决大多数输出问题。

此外,在实数运算问题(FP是在早期昂贵的计算机上为之发明的问题)上,宇宙的物理常数和所有其他测量值只为相对较少的有效数字所知,因此整个问题空间无论如何都是“不精确的”。FP“精度”在这种应用中不是问题。

当人们尝试使用FP进行计数时,整个问题就真的出现了。它确实可以做到这一点,但前提是你坚持使用整数值,这会破坏使用它的意义。这就是为什么我们拥有所有这些小数软件库的原因。

我喜欢克里斯的披萨回答,因为它描述了实际问题,而不仅仅是关于“不准确”的通常手写。如果FP只是“不准确”,我们可以修复它,而且几十年前就已经做到了。我们没有这样做的原因是因为FP格式紧凑快速,是处理大量数字的最佳方式。此外,这也是太空时代和军备竞赛以及早期使用小型内存系统解决速度非常慢的计算机的大问题的尝试所留下的遗产。(有时,单个磁芯用于1位存储,但这是另一回事。)

结论

如果您只是在银行数豆子,那么首先使用十进制字符串表示的软件解决方案工作得非常好。但你不能这样做量子色动力学或空气动力学。

存储在计算机中的浮点数由两部分组成,一部分是整数,另一部分是基数乘以整数部分的指数。

如果计算机在基数为10的情况下工作,则0.1将是1 x 10⁻¹,0.2将是2 x 10⁻¹,0.3将是3 x 10⁻¹. 整数运算简单而准确,所以加上0.1+0.2显然会得到0.3。

计算机通常不以10为基数工作,而是以2为基数工作。对于某些值,仍然可以得到精确的结果,例如0.5是1 x 2⁻¹和0.25是1 x 2⁻²,将它们相加,结果为3 x 2⁻²或0.75。确切地

问题是数字可以精确地以10为基数表示,但不能以2为基数。这些数字需要四舍五入到最接近的相等值。假设非常常见的IEEE 64位浮点格式,最接近0.1的数字是3602879701896397 x 2⁻⁵⁵, 最接近0.2的数字是7205759403792794 x 2⁻⁵⁵; 将它们相加,得到10808639105689191 x 2⁻⁵⁵, 或精确的十进制值0.30000000000000000444089209850062616169452667236328125。浮点数通常四舍五入以显示。

可以在数字计算机中实现的浮点数学必须使用实数的近似值及其运算。(标准版文件长达50多页,并有一个委员会处理其勘误表和进一步完善。)

这种近似是不同类型的近似的混合,每一种都可以被忽略或仔细考虑,因为其偏离精确性的特定方式。它还涉及到许多硬件和软件层面的明确例外情况,大多数人都会走过来假装没有注意到。

如果您需要无限精度(例如,使用数字π,而不是其许多较短的替代项之一),您应该编写或使用符号数学程序。

但是,如果您同意浮点数学有时在值和逻辑上是模糊的,错误可能会很快累积,并且您可以编写需求和测试来考虑这一点,那么您的代码可以经常通过FPU中的内容。