Java – Accuracy of Calculator Algorithms

algorithmsjava

I am building a library class that provides functionality for mathematical operations on BigDecimals (and a few on BigIntegers). Now, BigIntegers are quite easy to master and pleasant to use. BigDecimals can be tricky, but in the end, it finally pays off.

I want my class to provide results accurate up to a specified level of accuracy (like, number of places after the decimal point). This is where Math fails me because it supports operations on double. Now double supports 15-16 significant digits (not 15-16 places after decimal.) Hence, I've decided to upgrade to Bigdecimal.

But now, the problem is where can I find algorithms that support arbitrary precision calculations? Moreover, I would like to make my BigMath(so I call it) analogous to its sibling in the java.lang package. Then it struck me that if I opt for algorithms used by scientific calculators, then I might achieve the required level of accuracy.

So here are my (related) questions:

  1. Can I achieve the required level of accuracy using calculator algorithms?
  2. If yes, then where can I find the required algorithms?
  3. Are there any caveats lurking in my approach?

UPDATE:
Right now, I have just added support for the sqrt(BigDecimal) method. It makes use of the Newtonian method (and a special trick to generate a good estimate).

So, here's the entire BigMath class:

package in.blogspot.life_on_the_heap.bigmath;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;

/**
 * @author ambigram_maker
 */
public class BigMath {

    public static void main(String[] args) {
        BigDecimal number = new BigDecimal("91.91");
        BigDecimal sqrt = sqrt(number);
        System.out.println("number = " + number);
        System.out.println("sqrt(number) = " + sqrt);
        System.out.println("number = " +
                                   sqrt.multiply(sqrt, getContext(sqrt, DEFAULT_PREC))
                                           .stripTrailingZeros());
    }

    public static final BigDecimal TWO = BigDecimal.valueOf(2L);

    /*
     * The default number of places after the decimal. This is also the
     * *minimum* precision provided by this class.
     */
    private static final int DEFAULT_PREC = 20;
    /*
     * Maintains the "precision" or the number of digits after the decimal.
     */
    private static int precision = DEFAULT_PREC;

    public static void setPrecision(int precision_) {
        if (precision_ < DEFAULT_PREC) {
            precision = DEFAULT_PREC;
        } else {
            precision = precision_;
        }
    }

    public static int getPrecision() {
        return precision;
    }

    public static BigDecimal sqrt(BigDecimal decimal) {
        return sqrt(decimal, precision);
    }

    public static BigDecimal sqrt(BigDecimal decimal, int P_A_D) {
        // quick exit checks:
        if (decimal.compareTo(BigDecimal.ZERO) < 0) {
            return BigDecimal.valueOf(Double.NaN);
        }
        BigDecimal
                answer,     // The storage for the guesses.
                original,   // The result of squaring the guesses
                epsillon;   // The tolerance of this method.
        MathContext context = getContext(decimal, P_A_D);
        {
        /*
         * Obtain a good estimate of the square-root for the initial guess.
         * This is done by obtaining the "top" half of the bits of the decimal.
         */
            BigInteger integer = decimal.toBigInteger();
            answer = new BigDecimal
                             (integer.shiftRight(integer.bitLength() >>> 1));
        }
        original = answer.multiply(answer);
        epsillon = getEpsillon(P_A_D);
        while (original.subtract(decimal).abs().compareTo(epsillon) > 0) {
            answer = answer.subtract(original.subtract(decimal)
                                             .divide(TWO.multiply(answer),
                                                            context));
            original = answer.multiply(answer);
        }
        return answer.round(context);
    }

    public static BigDecimal getEpsillon(int precision) {
        return new BigDecimal("1E-" + precision);
    }

    private static MathContext getContext(BigDecimal decimal, int precision) {
        int beforePoint = (decimal.toString()).indexOf('.');
        if (beforePoint == -1) beforePoint = decimal.toString().length();
        return new MathContext(beforePoint + precision);
    }
}

The output is the desired one:

number = 91.91
sqrt(number) = 9.586970324351692703533
number = 91.91

Because I am quite happy with the result, I am determined to move ahead. Hence, I seek advice.

Best Answer

As people are hinting in the comments, if you want to do this for the fun of doing it, that's one thing, but if you want a library of scientific functions to use, you should use one that's already been written by experts. There's a list at http://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic#Libraries

The open-source libraries listed there are also a place to look for (if not find!) readable code.

Algorithms like this are part of numerical analysis; see Wikipedia for that, too. The famous "Numerical Recipes" books give both code and discussion for many kinds of calculations.

If you're talking about physical electronic calculators (as opposed to programs that are also called "calculators"), there are three reasons why I doubt you could use calculator code as a prototype for your arbitrary precision math library.

  • I don't know of any calculator company that has released the software and hardware designs of its calculators.

  • I've never heard of an electronic calculator with arbitrary precision arithmetic. A typical scientific calculator has a fixed precision. There will be no clue within the code how to extend a given function to a given larger precision (whereas the writers of, e.g., GNU MPFR will have solved these problems). For instance, what precision intermediate results will it need? How many more times will you need to go through the loops? Is the algorithm one that will work reasonably fast, or will it slow down too much, as you add precision?

  • A given calculator may have very advanced math hardware (to do Taylor series for instance) or very primitive hardware (can't even multiply two-digit numbers). The code will probably be hand-written machine code without more-readable source code. You'd need to understand and emulate both the functions of the hardware and code in your software. An interesting hobby but maybe not the one you were thinking of.

Related Topic