仅使用加法和乘法来实现除法

时间:2013-08-29 14:06:46

标签: c# math division

我在C#中编写BigDecimal类。我已经成功实施了+, - 和*运营商。但我无法找到计算2个BigDecimals除法的方法。使用这3个运营商实施部门的最快捷方式是什么?或者有更好的方法吗? (考虑开发时间和算法速度)

目标1:我希望结果是具有固定精度的另一个BigDecimal(应该是可更改的)

目标2:正如您所提到的,BigDecimal的目的不是固定的精度。那么我怎样才能实现无限精度

另一个问题:使用Microsoft BCL的BigRational类进行任意精度算术,然后使用Christopher Currens&s更好(关于速度和灵活性)这个线程中的扩展方法:Is there a BigFloat class in C#?得到十进制表示而不是写一个新类?

3 个答案:

答案 0 :(得分:9)

首先,我认为通过“大小数”表示你代表的是理性,其中分母被限制为10的任何幂。

你会想要想想你想要两位小数除法的输出。小数通过加法,减法和乘法关闭,但不会在除法之外结束。也就是说,任何两个小数乘以一起产生第三个:(7/10)*(9/100)给你63/1000,这是另一个小数。但除以这两位小数,你得到一个在分母中没有10的幂的理性。

回答你实际问过的问题:正如在循环中可以在加法中构建乘法一样,除法可以在循环中从减法构建。要将23除以7,请说:

  • 是7< 23?是。
  • 从23减去7得到16。
  • 是7< 16?是。
  • 从16减去7得到9
  • 是7< 9?是。
  • 从9减去7得到2。
  • 是7< 2?不,我们有第一个数字。我们做了三次减法,所以第一个数字是3。
  • 乘以2乘以10得到20
  • 是7< 20?是。
  • 从20减去7得到13。
  • 是7< 13?是。
  • 从15减去7得到6。
  • 是7< 6?不。我们做了两次减法并乘以10,所以下一个数字是2。
  • 乘以6乘以10得到60
  • 是7< 60?是的......
  • ...
  • 我们做了8次减法,所以下一个数字是8 ......
  • ......等等
  

你知道为此目的更快的算法吗?

当然,有很多更快的分割算法。这是一个: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)

  • 您可以查看Java BigDecimal实现的一些想法
  • 对于计算a / b,您可以使用Binary search algorithm来查找b * c = a的c(您应该运行算法直到达到所需的精度)。
  • 此外,您可以查看此BigFloat类,它具有指数形式的有趣实现
  • 对于无限精确度,您可以将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)

Eric Lippert的答案看起来很像“以最慢的方式进行长时间划分”。它准确,预算不快。假设您有一种方法可以使用double逼近BigDecimal,并且由于您已经在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算术找到错误,然后使用双算术将其除去)。我认为这应该是一种相对简单而有效的方法。

我使用floatdouble实现了上述版本 - 只是为了向自己证明这个原则可行。使用简单的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