对于一些大学工作,我必须近似一些数字——比如带有系列的欧拉。因此我必须添加非常小的数字,但我的精度有问题。如果数字非常小,则不会影响结果。
real s; //sum of all previous terms
ulong k; //factorial
s += 1.0/ k;
在每一步之后,k 变得更小,但在第 10 轮之后,结果不再变化并停留在 2.71828
对于一些大学工作,我必须近似一些数字——比如带有系列的欧拉。因此我必须添加非常小的数字,但我的精度有问题。如果数字非常小,则不会影响结果。
real s; //sum of all previous terms
ulong k; //factorial
s += 1.0/ k;
在每一步之后,k 变得更小,但在第 10 轮之后,结果不再变化并停留在 2.71828
固定精度浮点类型,即 CPU 的浮点单元(float
, double
, real
)本机支持的类型,对于任何需要多位数精度的计算(例如您给出的示例)来说并不是最佳的。
问题是这些浮点类型具有有限数量的精度数字(实际上是二进制数字),这限制了可以由这种数据类型表示的数字的长度。该float
类型有大约 7 个十进制数字的限制(例如 3.141593);double
类型限制为 14 个(例如 3.1415926535898);并且real
类型也有类似的限制(略多于double
)。
因此,将非常小的数字添加到浮点值将导致这些数字丢失。观察当我们将以下两个浮点值相加时会发生什么:
float a = 1.234567f, b = 0.0000000001234567
float c = a + b;
writefln("a = %f b = %f c = %f", a, b, c);
两者a
和b
都是有效的浮点值,并且各自保留大约 7 位的精度。但是当添加时,只保留最前面的 7 位数字,因为它被推回到一个浮点数中:
1.2345670001234567 => 1.234567|0001234567 => 1.234567
^^^^^^^^^^^
sent to the bit bucket
所以c
最终等于,a
因为加法a
和b
被击倒的精度更高。
这是对该概念的另一种解释,可能比我的要好得多。
这个问题的答案是任意精度的算术。不幸的是,对任意精度算术的支持不在 CPU 硬件中。因此,它(通常)不在您的编程语言中。但是,有许多库支持任意精度浮点类型以及您想要对其执行的数学运算。有关一些建议,请参阅此问题。您今天可能不会找到任何 D 特定的库用于此目的,但是有很多 C 库(GMP、MPFR 等)应该很容易单独使用,如果您能找到更是如此其中之一的 D 绑定。
如果您需要一个使用本机类型运行的解决方案,您应该能够通过尝试始终添加相似数量的数字来获得合理的结果。一种方法是计算系列的前 X 项,然后用总和重复替换两个最小的数字:
auto data = real[N];
foreach(i, ref v; data) {
v = Fn(i);
}
while(data.length > 1) {
data.sort(); // IIRC .sort is deprecated but I forget what replaced it.
data[1] += data[0];
data = data[1..$];
}
return data[0];
(最小堆会使这更快一些。)
As already mentioned you need to use some third-party multi-precision floating-point arithmetic library (I think Tango or Phobos only has a module for integer arithmetic of arbitrary length).
dil is a D project that uses MPFR. You should find bindings there.