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 函数会处理它们还是我应该自己做?每次我们谈到这些功能时,都必须向初学者指出这一点,否则他们都会偶然发现这个问题。

  • 问题标题的简短回答:是的。
  • 是的,有问题,或者是的,它被处理了?
  • 请参阅 Python 文档 – 浮点算术:问题和限制。此外,这并不特定于任何特定语言:What Every Computer Scientist Should Know About Floating-Point Arithmetic。
  • 您应该将”浮点数”视为 C 和 C 中的内容。所有二进制浮点数都有以 2 为底的有限表示(就此而言,以 10 为底)。确实,在数学中,实数在基数 2(或任何整数基数)中没有有限表示,但不要将它们视为浮点数。那么问题的答案是”是的,Python 使用浮点数及其局限性”。
  • 关于更新:math.ceil((0.1 + 0.2)*10) 是 4.0。所以不, ceil 不”照顾”浮点问题。
  • 认为”浮点数可以是有限的和无限的”是错误的。计算中的浮点数只不过是数学应用程序处理的实数的近似表示。表示总是有限的,除非你能肯定地证明相反,否则必须被认为是不精确的。有可以精确表示的实数的可计算子类。最有用的是有理数。与大多数语言不同,Haskell 开箱即用地支持精确表示有理数,但那是另一回事。
  • 如果您将 0.1 和 0.2 替换为它们的 decimal.Decimal 表示,它会按预期工作(即,正如不知道浮点数的人所期望的那样)。
  • 关于您的更新:您必须自己处理它,因为没有一个好的解决方案。您确实需要阅读(并理解)@LukasGraf 引用的第二个参考资料,然后才能安全地在浮点方面做很多事情。
  • 你的问题被误导了。首先,在 42 和 43 之间没有任何数可以合理地称为无限;此外,所有浮点数都有有限长度的十进制扩展。其次,在任何健全的浮点系统中,99.0 / 3.0 总是导致 33.0。此外,您似乎正在寻找十进制浮点数。十进制浮点数相当慢,无法解决任何实际问题。


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

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

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

  • C 和 C 用于表示 float 和 double 的格式不是标准化的,实际上因实现而异。 (并非所有现代 CPU 都使用 IEEE754。最明显的例外是 IBM 大型机,但事实上,大多数大型机都有专有格式。就此而言,很少使用 base 2。)
  • @JamesKanze 现代 IBM 大型机具有 IEEE754 单元。它们也只有非 IEEE754 浮点单元。
  • @PhilipJF 然而,我上次查看时,IEEE754 单元明显比原生浮点慢,并且没有人实际使用它们。
  • @JamesKanze:你有任何链接来支持它吗?我记得听说 System z10 上的 Linux 倾向于使用 IEEE 754 单元(但现在我当然不知道我想我是在哪里听到的)。我很想看到十六进制浮点单元用户的证据。
  • (我应该补充一点,我有一个不可告人的动机;我试图说服自己,未来版本的 Python 坚持 IEEE 754 是可以的。我想了解可能会在各种系统。)
  • @MarkDickinson 好吧,自从我与大型机 IBM 交互以来已经有一段时间了,但我知道我们必须将 BCD 和浮点都转换为 Java 浮点(保证 IEEE)。我怀疑大多数 Fortran 程序在不使用 BCD 时仍然使用 IBM 浮点和 COBOL。 (当然,COBOL 仍然是 IBM 大型机上使用最多的语言。)
  • @MarkDickinson 对于 Python,您可能是安全的;确实使用旧格式的程序可能很普遍,但它们可能都在传统的 IBM 环境中运行,我怀疑 Python 会出现在那里。 (据我所知,脚本语言仍然是 JCL。)另一方面,如果您使用 Python 调用其他语言(就像我们在 Windows 下所做的那样),那么使用相同的浮点将是一个明显的优势格式为其他语言。
  • @JamesKanze:谢谢;这很有帮助。


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

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

Prelude> round $ 99/3
33

虽然ghci用浮点计算除法。

唯一总是不安全的东西:

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

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

    来”模拟”相等性

  • 我想知道国家之间的数学有什么不同还是什么? 5/3 = 1.(6) – 无限浮点数。因为它正好是 5/3,但你永远无法将 5/3 表示为以 10 为底的浮点数,只能无限接近。但是,您可以提供精确的有限值作为其他基数中的浮点数。
  • @BarafuAlbino:”1.(6) – 无限浮点数”。没有。重复小数(在这种情况下是周期性的)。数字本身是完全有限的。但是,它在十进制系统中的表示将由无限多的数字组成。数字本身是完全有限的。该数字将始终是有限的,但是,表示可能不是,具体取决于系统。


除了这里的其他精彩答案,粗略地说,无论您使用哪种语言与 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

  • 哇。我不得不承认我可能跳过了介绍部分。
  • 我将其称为功能,而不是问题。如有必要,Python 确实有办法绕过它们。


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

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

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


来源:https://www.codenong.com/22811050/

微信公众号
手机浏览(小程序)
0
分享到:
没有账号? 忘记密码?