Python 和 Haskell 有 C/C++ 的浮点数问题吗?

Do Python and Haskell have the float uncertanity issue of C/C++?

本文关键字:浮点数 问题 C++ Haskell Python      更新时间:2023-10-16

首先,我没有用英语学习数学,所以我可能会在课文中使用错误的单词。

浮点数可以是有限的(42.36)

和无限的(42.363636...)

在 C/C++ 中,数字以 2 为基数存储我们的大脑在10基数上运行浮动。

问题是——

many (a lot, actually) of float numbers with base 10, that are finite, have no exact finite representation in base 2, and vice-versa.

大多数时候,这并不意味着什么。双精度的最后一位可能相差 1 位 - 没问题。

当我们计算两个实际上是整数的浮点数时,会出现一个问题。 99.0/3.0 C++可能导致33.032.9999...99。如果你把它转换成整数,那么 - 你会感到惊讶。出于这个原因,我总是在 C 中四舍五入之前添加一个特殊值(给定类型和体系结构的最小值为 2*)。我应该在Python中做

我已经在 Python 中运行了一些测试,似乎浮点除法总是按预期结果。但是一些测试是不够的,因为问题是依赖于架构的。有人确定它是否得到了处理,以及在什么级别上 - 在浮点型本身还是仅在舍入和缩短函数中?

附言如果有人能为哈斯克尔澄清同样的事情,我才刚刚开始 - 那就太好了。

更新人们向一份官方文件指出,浮点运算存在不确定性。剩下的问题是 - mathceil这样的功能是照顾它们还是我应该自己做?每次我们谈论这些功能时,都必须向初学者指出这一点,否则他们都会绊倒这个问题。

用于表示浮点数和双精度的格式 C 和C++是标准化的 (IEEE 754),您描述的问题在该表示中是固有的。由于 Python 是用 C 语言实现的,因此它的浮点类型容易出现相同的舍入问题。

Haskell的Float和Double是一个更高级别的抽象,但由于大多数(所有?)现代CPU使用IEEE754进行浮点计算,因此您很可能也会遇到这种舍入误差。

换句话说:只有选择将其浮点类型基于底层体系结构的语言/库才能在一定程度上规避IEEE754舍入问题,但由于底层硬件不直接支持其他表示,因此必须存在性能损失。因此,可能大多数语言都会坚持该标准,尤其是因为它的局限性是众所周知的。

实数本身,包括浮点数,在任何数学意义上都不是"无限的"。它们可能有无限的十进制表示,但这只是我们编写它们(或将它们存储在计算机中)方式的技术问题。事实上,IEEE754还指定了 +∞ 和 -∞ 值,这些是实际的无穷大......但它们不代表实数,并且在数学上在许多方面非常可怕。

也。。。"如果你把它转换成整数,那么"无论如何你永远不应该将浮点数"转换"为整数,这是不可能的:你只能将它们四舍五入为整数。如果你用例如哈斯克尔的round这样做,它确实很安全,当然

前奏>轮 $ 99/3
33

虽然ghci用浮点计算除法。

唯一总是不安全的事情:

  • 当然,从 float 到 int 的隐式转换是完全疯狂的,在 C 语言中肯定是一个错误。Haskell和Python都是正确的强类型,所以这样的事情不会偶然发生。

  • 浮点通常不应期望与任何特定内容完全相等。无论如何,期望这样并不是很有用,因为对于实际实数来说,任何一个都是空集,这大致意味着两个实数相等的唯一方法是如果有如此深刻的数学原因。但是对于任何分布,例如来自物理过程的分布,相等的概率正好为零,那么为什么要检查呢?
    只有数字 OTOH 与 < 进行比较是完全安全的(除非您正在处理大数字之间的非常小的差异,或者您使用它通过检查>来"模拟"相等性)。

是的,这是Python中的一个问题。

见 https://docs.python.org/2/tutorial/floatingpoint.html

Python 在内部将数字表示为 C 双精度,因此您将遇到浮点运算固有的所有问题。但它也包括一些算法来"修复"明显的案例。你给出的例子,32.99999...被公认为 33.0。从 Python 2.7 和 3.1 开始,他们使用 Gay 的算法来做到这一点;即,舍入回原始值的最短字符串。您可以在 Python 3.1 发行说明中看到说明。 在早期版本中,它只是四舍五入到小数点后 17 位。

正如他们自己警告的那样,这并不意味着它将作为十进制数字工作。

>>> 1.1 + 2.2
3.3000000000000003
>>> 1.1 + 2.2 == 3.3
False

(但这应该已经敲响了你的警钟,因为比较浮点数的相等从来都不是一件好事)

如果要确保精确到小数位数(例如,如果您正在处理财务),则可以使用标准库中的模块小数。如果要表示小数,可以使用分数,但它们都比纯数慢。

>>> import decimal
>>> decimal.Decimal(1.1) + decimal.Decimal(2.2) 
Decimal('3.300000000000000266453525910')
# Decimal is getting the full floating point representation, no what I type!
>>> decimal.Decimal('1.1') + decimal.Decimal('2.2')
Decimal('3.3')
# Now it is fine.
>>> decimal.Decimal('1.1') + decimal.Decimal('2.2') == 3.3
False
>>> decimal.Decimal('1.1') + decimal.Decimal('2.2') == decimal.Decimal(3.3)
False
>>> decimal.Decimal('1.1') + decimal.Decimal('2.2') == decimal.Decimal('3.3')
True

除了这里的其他精彩答案之外,粗略地说,无论您使用哪种语言进行交互,IEEE754都有完全相同的问题,我想指出,许多语言都有用于其他类型的数字的库。一些标准方法是使用定点算术(许多,但不是全部,IEEE754的细微差别来自浮点)或有理数。Haskell还库了可计算实数和圆数。

此外,由于它的typeclass机制,使用这些替代类型的数字在Haskell中特别方便,这意味着用这些其他类型的数字做算术看起来和感觉完全一样,用你通常的IEEE754 Float s和Double s做算术;但是你会得到替代类型的更好(和更差!)的属性。例如,通过适当的导入,您可以看到:

> 99/3 :: Double
33.0
> 99/3 :: Fixed E12
33.000000000000
> 99/3 :: Rational
33 % 1
> 99/3 :: CReal
33.0
> 99/3 :: Cyclotomic
33
> 98/3 :: Rational
98 % 3
> sqrt 2 :: CReal
1.4142135623730950488016887242096980785697
> sqrtInteger (-5) :: Cyclotomic
e(20) + e(20)^9 - e(20)^13 - e(20)^17

Haskell不要求Float和Double是IEEE单精度和双精度浮点数,但它强烈建议这样做。 全康遵循建议。 IEEE 浮点数在所有语言中都有相同的问题。 其中一些是由LIA标准处理的,但Haskell只在"库"中实现。 (不,我不确定是什么库或是否存在。

这个伟大的答案显示了各种其他数字表示,这些表示形式要么是Haskell的一部分(如Rational),要么可以从hackage获得,如(Fixed,CReal和Cyclotomic)。

Rational、Fixed和Cyclotomic可能有类似的Python库;"固定"有点类似于 .Net Decimal 类型。 CReal 也可能,但我认为它可能会利用 Haskell 按需调用,并且可能很难直接移植到 Python;它也很慢。