Python 和 Haskell 是否存在 C/C 的浮动不确定性问题?

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

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

浮点数可以是有限的(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.

这在大多数情况下没有任何意义。 double 的最后一位数字可能会偏移 1 位 - 不是问题。

当我们计算两个实际上是整数的浮点数时,就会出现问题。 C 上的 99.0/3.0 可以产生 33.0 以及 32.9999...99。如果您将其转换为整数,那么您会大吃一惊。出于这个原因,我总是在 C 中四舍五入之前添加一个特殊值(给定类型和体系结构的 2* 最小值)。我应该用 Python 来做吗?

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

附言如果有人可以为我刚刚开始的 Haskell 澄清同样的事情 - 那就太好了。

更新
人们在一份官方文件中指出,浮点运算存在不确定性。剩下的问题是 - 像 ceil 这样的 math 函数会处理它们还是我应该自己做?每次我们谈到这些功能时,都必须向初学者指出这一点,否则他们都会偶然发现这个问题。


C 和 C 用于表示 float 和 double 的格式是标准化的(IEEE 754),您描述的问题是该表示所固有的。由于 Python 是用 C 实现的,它的浮点类型容易出现相同的舍入问题。

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

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


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

另外... "如果你把它转换成整数,那么" 你永远不应该 "convert" 浮点数转换成整数,这是不可能的:你只能将它们四舍五入成整数。如果你这样做,例如Haskell的round,确实很安全,当然

Prelude> round $ 99/3
33

虽然ghci用浮点计算除法。

唯一总是不安全的东西:

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

  • 通常不应期望浮点数与任何特定值完全相等。无论如何,期望这样并不是真的有用,因为对于实际实数,任何一个都是空集,这大致意味着两个实数相等的唯一方法是如果有这么深的数学原因。但是对于任何发行版,例如从物理过程来看,相等的概率正好为零,那你为什么要检查呢?只有比较数字 OTOH 和 < 是完全安全的(除非你正在处理巨大数字之间的非常小的差异,或者你使用它还通过检查 >).

    来"模拟"相等性


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

此外,由于其类型类机制,在 Haskell 中使用这些替代类型的数字特别方便,这意味着使用这些其他类型的数字进行算术运算的外观和感觉与您通常使用的 IEEE754 进行算术运算完全相同 Float s 和 Doubles;但是你得到了替代类型的更好(和更糟!)的属性。例如,通过适当的导入,您可以看到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
> 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

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

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

1
2
3
4
>>> 1.1 + 2.2
3.3000000000000003
>>> 1.1 + 2.2 == 3.3
False

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> 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

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

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


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

这个很好的答案显示了其他各种数字表示,它们要么是 Haskell 的一部分(如 Rational),要么可从 hackage 中获得,如(Fixed、CReal 和 Cyclotomic)。

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