我正在学习 C,并且出现了保护数字和舍入错误的想法。脚本语言的从业者(我在这里想到的是 Python 和 Perl)需要担心这些东西吗?如果他们在做科学编程怎么办?
9 回答
这取决于。 double
s 在任何地方的行为都是一样的,所以如果你用双打做数学,任何语言都会有同样的问题。如果您使用本机任意精度类型,那么不,这不是问题。考虑:
use Math::BigFloat;
my $big = Math::BigFloat->new("1_000_000_000_000_000_000_000");
my $small = Math::BigFloat->new("0.000000000000000000000000001");
print $big + $small;
(或者,如果你真的想隐藏正在发生的事情:
use bignum;
print 1_000_000_000_000_000_000_000 + 0.000000000000000000000000001
)
正如预期的那样,这会产生:
1000000000000000000000.000000000000000000000000001
同样如预期的那样,这不是在一条 CPU 指令中完成的。
我不得不不同意 Lutz... 虽然您提到的舍入错误确实存在于 Python/Perl/Ruby 中,但它们与 C 中实现的语言完全无关。问题远不止于此。
与所有数据一样,浮点数在现代计算机上以二进制表示。正如有周期性十进制表示的数字(例如,1/3 = 0.333333...),也有周期性二进制表示的数字(例如,1/10 = 0.0001100110011...)。由于这些数字不能在(有限数量的)计算机内存中精确表示,因此涉及它们的任何计算都会引入错误。
这可以通过使用高精度数学库来解决,该库将数字表示为分数的两个数字(即“分子 = 1,分母 = 10”)或字符串,而不是使用本机二进制表示。然而,由于对存储为其他东西的数字进行任何计算涉及额外的工作,这些库必然会减慢必须通过它们的任何数学运算。
Python中有几种类型的非整数:
x = 1 / 2
会给你标准的 float。它的类型是float
,它与 C 中的基本相同,由硬件处理,并且与float
世界上其他所有问题都有相同的问题。
但是,也有小数类型:
from fractions import Fraction
x = Fraction(1, 2)
它具有与有理数的精确算术。
如果您想要进行舍入,但对计算机上有意义的数字数量不满意,或者它可能因平台而异,那么小数类型是您的朋友:
from decimal import Decimal
x = Decimal('0.5')
如果您愿意,您可以将其精度设置为 100 位。或者将其设置为 2 用于银行应用程序。
只要计算机是愚蠢的,我们就可能需要这么多不同的类型。至少,根据 Pythonic 原则,Python 要求您明确选择您想要从数字中得到什么。
此外,精确算术不会导致舍入问题是一个很大的误解。任何时候您将精确值舍入以对用户做一些有用的事情——例如将其打印给用户或将那么多美元添加到用户的银行账户——你会遇到舍入的“奇怪行为”。这是非整数算术所固有的。
这取决于您如何表示您的数字,而不是您使用的语言。
例如,如果我在 8051 汇编中编写了所有代码,但已经实现了一个光滑的有理数库,那么舍入不是问题。1/3 只等于 1/3。
但是,如果我使用最新的时髦动态语言,并且它使用 IEE754 浮点数,那么 IEEE754 的所有限制都适用。
如果您需要关心生成的数字的细节,那么您需要了解它们的表示方式以及它们是如何被您选择的工具所操纵的。
更新:
PDL是在 Perl 中进行科学计算的流行库。
您可以使用 Python 和外部模块进行多精度计算。官方网站的多精度数学部分列出了其中的许多。
好吧,你不能幸免于 Ruby 中的浮点错误。例如:
irb(main):033:0> (2.01 * 1000).to_i
=> 2009
irb(main):034:0> ((2.01 * 1000.0) + 0.5).floor
=> 2010
他们当然知道!
Python 2.6 的一个例子:
>>> 1440.0 / 900.0
1.6000000000000001
正如 lutz 所说,由于脚本语言通常用 C 实现,它们继承了这些“特性”。用语言对它们进行补偿无疑意味着在性能或可移植性方面进行某种权衡。
当您进行科学编程时,无论您使用哪种编程语言或数值库,您总是需要担心舍入错误。
证明:假设你想追踪一个分子在宇宙边界附近的运动。宇宙的大小约为 930 亿光年(据我们所知)。分子非常小,因此您至少需要纳米精度 (10^-6)。那是50个数量级。
出于某种原因,您需要旋转该分子。这涉及到sin()
运算cos()
和乘法。乘法不是问题,因为有效数字的数量只是两个操作数长度的总和。但是怎么样sin()
?
您必须创建误差方程以确保保留足够的位数,以便最终结果具有已知的最大误差。我不知道任何可以自动执行此操作的“简单”数字库(例如,作为调用的一部分sin()
)。这是你需要Matlab或类似的东西的地方。