我在C#中编写BigDecimal类。我已经成功实施了+, - 和*运营商。但我无法找到计算2个BigDecimals除法的方法。使用这3个运营商实施部门的最快捷方式是什么?或者有更好的方法吗? (考虑开发时间和算法速度)
目标1:我希望结果是具有固定精度的另一个BigDecimal(应该是可更改的)
目标2:正如您所提到的,BigDecimal的目的不是固定的精度。那么我怎样才能实现无限精度?
另一个问题:使用Microsoft BCL的BigRational
类进行任意精度算术,然后使用Christopher Currens&s更好(关于速度和灵活性)这个线程中的扩展方法:Is there a BigFloat class in C#?得到十进制表示而不是写一个新类?
答案 0 :(得分:9)
首先,我认为通过“大小数”表示你代表的是理性,其中分母被限制为10的任何幂。
你会想要想想你想要两位小数除法的输出。小数通过加法,减法和乘法关闭,但不会在除法之外结束。也就是说,任何两个小数乘以一起产生第三个:(7/10)*(9/100)给你63/1000,这是另一个小数。但除以这两位小数,你得到一个在分母中没有10的幂的理性。
回答你实际问过的问题:正如在循环中可以在加法中构建乘法一样,除法可以在循环中从减法构建。要将23除以7,请说:
你知道为此目的更快的算法吗?
当然,有很多更快的分割算法。这是一个:Goldschmidt的算法。
首先,我希望很明显,如果您尝试计算X / D
,那么您可以先计算1 / D
,然后将其乘以X
。此外,让我们假设WOLOG严格在0和1之间。
如果不是这样的话怎么办?如果D为负,则反转它和X;如果D为零,则给出错误;如果D为1则答案为X;如果D大于1则将它和X都除以10,这对你来说应该很容易,因为你的系统是十进制的。继续应用这些规则,直到D介于0和1之间。 (作为额外的优化:当D非常小时算法最慢,所以如果D小于,比如0.1,则将X和D乘以10,直到D大于或等于0.1。)
好的,我们的问题是我们在0和1之间有一个D,我们希望计算1 / D
。可能最简单的方法就是做一个例子。假设我们正在尝试计算1 / 0.7
。正确答案是1.42857142857...
首先从2减去0.7得到1.3。现在将分数的两个部分乘以1.3:
(1 / 0.7) * (1.3 / 1.3) = 1.3 / 0.91
大。我们现在已将1 / 0.7
计算为一位精度。
现在再做一次。从2减去0.91得到1.09。将分数的两个部分乘以1.09:
(1.3 / 0.91) * (1.09 / 1.09) = 1.417 / 0.9919
很好,现在我们有两个正确的数字。现在再做一次。从2减去0.9919得到1.0081。顶部和底部乘以1.0081:
(1.417 / 0.9919) * (1.0081 / 1.0081) = 1.4284777 / 0.99993439
嘿,现在我们有四个正确的数字。看看怎么样?分母越接近1的每一步,因此分子越接近1 / 0.7
。
这比收缩方法收敛得快得多。
你明白为什么会这样吗?
由于D在0和1之间,因此有一个数字E使得D = 1 - E,而E也在0和1之间。
当我们将D乘以(2 - D)时,我们将(1 - E)乘以(1 + E),得到1 - E 2 。
从0< E<在图1中,显然E 2 小于E并且也在0和1之间,这意味着1-E 2 更接近1 。实际上它是 lot 更接近1.通过多次重复这个过程,我们很快就接近1。实际上我们在这里做的是每一步的正确数字的数量大致加倍。显然,这比减法方法要好得多,减法方法在每一步上给出一个额外的数字。
继续这样做,直到你有你想要的准确性。由于每个步骤的准确位数大致加倍,因此您应该能够很快达到可接受的准确度。由于我们已经安排D大于或等于0.1开始,因此E绝不大于0.9;反复平方0.9可以很快将你降到很小的数字。
答案 1 :(得分:1)
BigDecimal
存储为两个BigInteger
的合理分数。有理数分数代数的代数:
(x1/x2) + (y1/y2) = (x1*y2+x2*y1)/(x2*y2)
(x1/x2) - (y1/y2) = (x1*y2-x2*y1)/(x2*y2)
(x1/x2) * (y1/y2) = (x1*y1)/(x2*y2)
(x1/x2) / (y1/y2) = (x1*y2)/(x2*y1)
double(固定精度)的实现示例:
public double Divide(double a, double b, double eps)
{
double l = 0, r = a;
while (r - l > eps)
{
double m = (l + r) / 2;
if (m * b < a)
l = m;
else
r = m;
}
return (l + r) / 2;
}
答案 2 :(得分:1)
BD
类中实现了+, - 和*,您可以(大致)执行以下操作:
BD operator/(BD a, BD b) {
BD r, result=0, residual;
double aa, bb, rr;
residual = a;
bb = b;
while (residual > desired) {
rr = (double)residual / bb;
r = rr; // assuming you have a conversion from double to bigdecimal with loss of precision
result += r;
residual = a - (result * b);
}
return result;
}
这将进行逐次逼近,但同时使用多个数字(基本上你使用BD算术找到错误,然后使用双算术将其除去)。我认为这应该是一种相对简单而有效的方法。
我使用float
和double
实现了上述版本 - 只是为了向自己证明这个原则可行。使用简单的C框架,只需要两次迭代就可以将精度降低到double
除法的水平。我希望你明白这一点。
#include <stdio.h>
double divide(double a, double b);
int main(void) {
double a, b, result;
float fa, fb, fr;
a = 123.5;
b = 234.6;
fa = a;
fb = b;
fr = fa / fb;
printf("using float: %f\n", fr);
result = divide(a, b);
printf("using double: %lf\n", result);
printf("difference: %le\n", result - fr);
}
double divide(double a, double b) {
double r, result=0, residual;
float aa, bb, rr;
residual = a;
bb = b;
while (residual > 1e-8) {
rr = (float)residual / bb;
r = rr; // assuming you have a conversion from double to bigdecimal with loss of precision
result += r;
residual = a - (result * b);
printf("residual is now %le\n", residual);
}
return result;
}
输出:
using float: 0.526428
residual is now 8.881092e-06
residual is now 5.684342e-14
using double: 0.526428
difference: 3.785632e-08