Reputation: 1097
I'm writing a BigDecimal class in C#. I've successfully implemented +, - and * operators. But I can't figure out a way to calculate division of 2 BigDecimals. What's the quickest way to implement division using those 3 operators? Or is there a better way to do that? (Considering both development time and algorithm speed)
Goal 1: I want the result to be another BigDecimal with a fixed precision (that should be changeable)
Goal 2: As you mentioned, the purpose of BigDecimal is not a fixed precision. So how can I achieve infinite precision?
Another Question: Is it better (regarding speed and flexibility) to use Microsoft BCL's BigRational
class for Arbitrary-precision arithmetic and then use Christopher Currens's extension method in this thread: Is there a BigFloat class in C#? to get decimal representation instead of writing a new class?
Upvotes: 2
Views: 2208
Reputation: 46425
Eric Lippert's answer looks a lot like "long division in the slowest possible way". It is accurate, budt it's not fast. Assuming that you have a way of approximating your BigDecimal with a double, and since you already have +, - and * implemented in your BD
class, you could do (approximately) the following:
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;
}
This will do a successive approximation, but many digits at once (basically you find the error using BD arithmethic, then divide that out using double arithmetic). I think that should be a relatively straightforward and efficient approach.
I have implemented a version of the above using float
and double
- just to prove to myself that the principle can work. Using a straightforward C framework, it takes just two iterations to get the precision down to the level of the double
division. I hope you get the idea.
#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;
}
Output:
using float: 0.526428
residual is now 8.881092e-06
residual is now 5.684342e-14
using double: 0.526428
difference: 3.785632e-08
Upvotes: 0
Reputation: 660377
First off, I presume that by "big decimal" you mean that you are representing a rational where the denominator is restricted to be any power of ten.
You'll want to think hard about what you want the output of the division of two decimals to be. Decimals are closed over addition, subtraction and multiplication but not closed over division. That is, any two decimals multiplied together produces a third: (7 / 10) * (9 / 100) gives you 63 / 1000, which is another decimal. But divide those two decimals and you get a rational that does not have a power of ten in the denominator.
To answer the question you actually asked: just as multiplication can be built out of addition in a loop, division can be built out of subtraction in a loop. To divide 23 by 7, say:
Do you know of a faster algorithm for this purpose?
Sure, there are lots of faster algorithms for division. Here's one: Goldschmidt's Algorithm.
First off, I hope that it is clear that if you are trying to compute X / D
then you can first compute 1 / D
and then multiply that by X
. Moreover, let us assume WOLOG that D is strictly between 0 and 1.
What if it isn't? If D is negative, invert both it and X; if D is zero, give an error; if D is 1 then the answer is X; if D is greater than 1 then divide it and X both by 10, which should be easy for you since your system is decimal. Keep applying those rules until you have a D between zero and one. (As an additional optimization: The algorithm is slowest when D is very small, so if D is less than, say, 0.1, multiply X and D by ten until D is greater than or equal to 0.1.)
OK, so our problem is that we have a number D between zero and one and we wish to compute 1 / D
. Probably the easiest thing to do is to work an example. Suppose we are trying to compute 1 / 0.7
. The correct answer is 1.42857142857...
Start by subtracting 0.7 from 2 to get 1.3. Now multiply both parts of the fraction by 1.3:
(1 / 0.7) * (1.3 / 1.3) = 1.3 / 0.91
Great. We have now computed 1 / 0.7
to one digit of accuracy.
Now do it again. Subtract 0.91 from 2 to get 1.09. Multiply both parts of the fraction by 1.09:
(1.3 / 0.91) * (1.09 / 1.09) = 1.417 / 0.9919
Great, now we have two correct digits. Now do it again. Subtract 0.9919 from 2 to get 1.0081. Multiply top and bottom by 1.0081:
(1.417 / 0.9919) * (1.0081 / 1.0081) = 1.4284777 / 0.99993439
Hey, now we have four correct digits. See how that goes? Every step of the way the denominator gets much closer to 1, and therefore the numerator gets closer to 1 / 0.7
.
This converges much more quickly than the subtraction method.
Do you see why it works?
Since D is between 0 and 1, there is a number E such that D = 1 - E, and E is also between 0 and 1.
When we multiply D by (2 - D), we are multiplying (1 - E) by (1 + E), which gives 1 - E2.
Since 0 < E < 1, clearly E2 is smaller than E and also between 0 and 1, which means that 1 - E2 is closer to 1. In fact it is a lot closer to 1. By repeating this process multiple times we get close to 1 very quickly. Effectively what we're doing here is roughly doubling the number of correct digits on each step. Obviously that is a lot better than the subtraction method, which gives one additional digit on each step.
Keep on doing that until you have the accuracy you desire. Since you're roughly doubling the number of accurate digits on each step, you should be able to get to an acceptable degree of accuracy pretty quickly. Since we already arranged that D is greater than or equal to 0.1 to start with, E is never larger than 0.9; repeatedly squaring 0.9 gets you down to a very small number pretty quickly.
Upvotes: 10
Reputation: 19031
BigDecimal
as rational fraction of two BigInteger
.Algebra for rational fraction represention:
(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)
Example of implementation for double (fixed precision):
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;
}
Upvotes: 1