2

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.

Rudy Velthuis
  • 28,387
  • 5
  • 46
  • 94
  • wolfram alpha is pretty precise for cos, see http://www.wolframalpha.com/input/?i=cos(12)+to+1000+digits –  Aug 20 '16 at 18:53
  • Ah, thanks, I will try to check my results with Wolfram Alpha. Good tip! – Rudy Velthuis Aug 20 '16 at 18:54
  • Solely an idea: If you do symbolic calculation, you can have lazily evaluated (infinite) series, combine them, have an error precision with every series, and maybe receive faster results. Using java 8 lambdas. – Joop Eggen Aug 20 '16 at 19:07
  • Hmmm... wolframalpha.com/input/?i=cos(0.5)+to+1000+digits (and setting radians) gives me exact the same output as my test code with precision 1000, so in this example, 5 is enough. Must try even more digits and many different values. I assume that input values should not be too far away from 0 either. – Rudy Velthuis Aug 20 '16 at 19:12
  • @Joop: Thanks for the suggestion, but as I wrote, this should be translatable to Delphi as well, and use BigDecimal. – Rudy Velthuis Aug 20 '16 at 19:13
  • FWIW, the results are the same for 10,000 digits too. I didn't expect this. – Rudy Velthuis Aug 20 '16 at 19:46
  • @Joop: one of my next steps is the implementation of `BigRational` in Delphi. I guess that should be pretty accurate too. But I will keep symbolic (lazily evaluated) math in mind. – Rudy Velthuis Aug 20 '16 at 19:56

1 Answers1

0

You can reduce numbers not in -pi>x>=pi to that range. The Taylor expansion for sin(x) gets less accurate as abs(x) increases, so reducing x down to this range will increase your accuracy for large numbers.

  • I know that. But even then, I still don't know if, say, 5 extra digits are enough. The same for the calculation of pi. To reduce the value to that range, I must have an at least equally precise value of pi. – Rudy Velthuis Dec 20 '16 at 07:07
  • you could try converting to degrees, correcting range, and then converting back to radians, but that itself could lead to some correction errors. – Deniz Olgun Dec 21 '16 at 17:07
  • To get the precision, converting to degrees would still require a pi of the same precision. So for a precision of, say, 10000, I would have to have a pi in that range too (precision plus 5 -- or whatever --, just to be sure). – Rudy Velthuis Dec 21 '16 at 19:21