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:
- Can I achieve the required level of accuracy using calculator algorithms?
- If yes, then where can I find the required algorithms?
- 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.