Introduction
I am interested in writing math functions for BigDecimal
(actually, also for
my own BigDecimal
type written in Delphi,
but that is irrelevant here -- in this question, I use Java's BigDecimal
because more people know it and
my BigDecimal
is very similar. The test code below is in Java and works fine and works equally well in the Delphi
translation).
I know that BigDecimal
is not fast, but it is pretty accurate. I do not want to use some existing Java BigDecimal
math library, especially not
because this is for my own BigDecimal
type (in Delphi) as well.
As a nice example of how to implement trig functions, I found the following simple example (but I forgot where, sorry). It obviously uses MacLaurin series to calculate the cosine of a BigDecimal, with a given precision.
Question
This precision is exactly my problem. The code below uses an extra precision of 5 to calculate the result and only in the end, it rounds that down to the desired precision.
I have a feeling that an extra precision of 5 is fine for, say, a target precision up to 50 or even a little more, but not for BigDecimals
with a much higher precision (say, 1000 digits or more). Unfortunately, I couldn't find a way to verify this (e.g. with an online extremely accurate calculator).
Finally, my question: am I right -- that 5 is probably not enough for larger numbers -- and if I am, how can I calculate or estimate the extra precision required?
Example code calculates cos(BigDecimal)
:
public class BigDecimalTrigTest
{
private List _trigFactors;
private int _precision;
private final int _extraPrecision = 5; // Question: is 5 enough?
public BigDecimalTrigTest(int precision)
{
_precision = precision;
_trigFactors = new Vector();
BigDecimal one = new BigDecimal("1.0");
BigDecimal stopWhen = one.movePointLeft(precision + _extraPrecision);
System.out.format("stopWhen = %s\n", stopWhen.toString());
BigDecimal factorial = new BigDecimal(2.0);
BigDecimal inc = new BigDecimal(2.0);
BigDecimal factor = null;
do
{
factor = one.divide(factorial, precision + _extraPrecision,
BigDecimal.ROUND_HALF_UP); // factor = 1/factorial
_trigFactors.add(factor);
inc = inc.add(one); // factorial = factorial * (factorial + 1)
factorial = factorial.multiply(inc);
inc = inc.add(one); // factorial = factorial * (factorial + 1)
factorial = factorial.multiply(inc);
} while (factor.compareTo(stopWhen) > 0);
}
// sin(x) = x - x^3/3! + x^5/5! - x^7/7! + x^9/9! - ... = Sum[0..+inf] (-1^n) * (x^(2*n + 1)) / (2*n + 1)!
// cos(x) = 1 - x^2/2! + x^4/4! - x^6/6! + x^8/8! - ... = Sum[0..+inf] (-1^n) * (x^(2*n)) / (2*n)!
public BigDecimal cos(BigDecimal x)
{
BigDecimal res = new BigDecimal("1.0");
BigDecimal xn = x.multiply(x);
for (int i = 0; i < _trigFactors.size(); i++)
{
BigDecimal factor = (BigDecimal) _trigFactors.get(i);
factor = factor.multiply(xn);
if (i % 2 == 0)
{
factor = factor.negate();
}
res = res.add(factor);
xn = xn.multiply(x);
xn = xn.multiply(x);
xn = xn.setScale(_precision + _extraPrecision, BigDecimal.ROUND_HALF_UP);
}
return res.setScale(_precision, BigDecimal.ROUND_HALF_UP);
}
public static void main(String[] args)
{
BigDecimalTrigTest bdtt = new BigDecimalTrigTest(50);
BigDecimal half = new BigDecimal("0.5");
System.out.println("Math.cos(0.5) = " + Math.cos(0.5));
System.out.println("this.cos(0.5) = " + bdtt.cos(half));
}
}
Update
A test with Wolfram Alpha for cos(.5) to 10000 digits
(as @RC commented) gives the same result as my test code for the same precision. Perhaps 5 is enough as extra precision. But I need more tests to be sure.