diff --git a/src/main/java/org/nwapw/abacus/number/NaiveNumber.java b/src/main/java/org/nwapw/abacus/number/NaiveNumber.java index 48beb1a..05dcc33 100755 --- a/src/main/java/org/nwapw/abacus/number/NaiveNumber.java +++ b/src/main/java/org/nwapw/abacus/number/NaiveNumber.java @@ -128,5 +128,9 @@ public class NaiveNumber extends NumberInterface { return Double.toString(Math.round(value * shiftBy) / shiftBy); } + @Override + public NumberInterface getMaxError(){ + return new NaiveNumber(Math.pow(10, -18)); + } } diff --git a/src/main/java/org/nwapw/abacus/number/NumberInterface.java b/src/main/java/org/nwapw/abacus/number/NumberInterface.java index 83007c2..34edfcc 100755 --- a/src/main/java/org/nwapw/abacus/number/NumberInterface.java +++ b/src/main/java/org/nwapw/abacus/number/NumberInterface.java @@ -255,4 +255,11 @@ public abstract class NumberInterface { return promoteToInternal(toClass); } + /** + * Returns the smallest error this instance can tolerate depending + * on its precision and value. + * @return the smallest error that should be permitted in calculations. + */ + public abstract NumberInterface getMaxError(); + } diff --git a/src/main/java/org/nwapw/abacus/number/PreciseNumber.java b/src/main/java/org/nwapw/abacus/number/PreciseNumber.java index 7d288b7..094e5a9 100755 --- a/src/main/java/org/nwapw/abacus/number/PreciseNumber.java +++ b/src/main/java/org/nwapw/abacus/number/PreciseNumber.java @@ -1,7 +1,7 @@ package org.nwapw.abacus.number; import java.math.BigDecimal; -import java.math.RoundingMode; +import java.math.MathContext; /** * A number that uses a BigDecimal to store its value, @@ -22,6 +22,21 @@ public class PreciseNumber extends NumberInterface { */ public static final PreciseNumber TEN = new PreciseNumber(BigDecimal.TEN); + /** + * The number of extra significant figures kept in calculations before rounding for output. + */ + private static int numExtraInternalSigFigs = 15; + + /** + * MathContext that is used when rounding a number prior to output. + */ + private static MathContext outputContext = new MathContext(50); + + /** + * MathContext that is actually used in calculations. + */ + private static MathContext internalContext = new MathContext(outputContext.getPrecision()+numExtraInternalSigFigs); + /** * The value of the PreciseNumber. */ @@ -48,7 +63,7 @@ public class PreciseNumber extends NumberInterface { @Override public int getMaxPrecision() { - return 65; + return internalContext.getPrecision(); } @Override @@ -58,7 +73,7 @@ public class PreciseNumber extends NumberInterface { @Override public NumberInterface divideInternal(NumberInterface divisor) { - return new PreciseNumber(value.divide(((PreciseNumber) divisor).value, this.getMaxPrecision(), RoundingMode.HALF_UP)); + return new PreciseNumber(value.divide(((PreciseNumber) divisor).value, internalContext)); } @Override @@ -147,7 +162,11 @@ public class PreciseNumber extends NumberInterface { @Override public String toString() { - BigDecimal rounded = value.setScale(getMaxPrecision() - 15, RoundingMode.HALF_UP); - return rounded.stripTrailingZeros().toPlainString(); + return value.round(outputContext).toString(); + } + + @Override + public NumberInterface getMaxError(){ + return new PreciseNumber(value.ulp()).multiplyInternal(TEN.intPowInternal(value.precision()-internalContext.getPrecision())); } } diff --git a/src/main/java/org/nwapw/abacus/plugin/StandardPlugin.java b/src/main/java/org/nwapw/abacus/plugin/StandardPlugin.java index 4d8aea2..d30c265 100755 --- a/src/main/java/org/nwapw/abacus/plugin/StandardPlugin.java +++ b/src/main/java/org/nwapw/abacus/plugin/StandardPlugin.java @@ -188,7 +188,7 @@ public class StandardPlugin extends Plugin { */ private NumberInterface getLogPartialSum(NumberInterface x) { - NumberInterface maxError = getMaxError(x); + NumberInterface maxError = x.getMaxError(); x = x.subtract(NaiveNumber.ONE.promoteTo(x.getClass())); //Terms used are for log(x+1). NumberInterface currentNumerator = x, currentTerm = x, sum = x; int n = 1; @@ -207,7 +207,7 @@ public class StandardPlugin extends Plugin { * @return the value of log(2) with the appropriate precision. */ private NumberInterface getLog2(NumberInterface number) { - NumberInterface maxError = getMaxError(number); + NumberInterface maxError = number.getMaxError(); //NumberInterface errorBound = fromInt(number.getClass(), 1); //We'll use the series \sigma_{n >= 1) ((1/3^n + 1/4^n) * 1/n) //In the following, a=1/3^n, b=1/4^n, c = 1/n. @@ -301,16 +301,11 @@ public class StandardPlugin extends Plugin { @Override protected NumberInterface applyInternal(NumberInterface[] params) { - NumberInterface maxError = getMaxError(params[0]); + NumberInterface maxError = params[0].getMaxError(); int n = 0; - if (params[0].signum() <= 0) { - NumberInterface currentTerm = NaiveNumber.ONE.promoteTo(params[0].getClass()), sum = currentTerm; - while (FUNCTION_ABS.apply(currentTerm).compareTo(maxError) > 0) { - n++; - currentTerm = currentTerm.multiply(params[0]).divide((new NaiveNumber(n)).promoteTo(params[0].getClass())); - sum = sum.add(currentTerm); - } - return sum; + if (params[0].signum() < 0) { + NumberInterface[] negatedParams = {params[0].negate()}; + return fromInt(params[0].getClass(), 1).divide(applyInternal(negatedParams)); } else { //We need n such that x^(n+1) * 3^ceil(x) <= maxError * (n+1)!. //right and left refer to lhs and rhs in the above inequality. @@ -352,7 +347,7 @@ public class StandardPlugin extends Plugin { return NaiveNumber.ONE.promoteTo(params[1].getClass()); //Detect integer bases: if(params[0].fractionalPart().compareTo(fromInt(params[0].getClass(), 0)) == 0 - && FUNCTION_ABS.apply(params[0]).compareTo(fromInt(params[0].getClass(), Integer.MAX_VALUE)) < 0 + && FUNCTION_ABS.apply(params[1]).compareTo(fromInt(params[0].getClass(), Integer.MAX_VALUE)) < 0 && FUNCTION_ABS.apply(params[1]).compareTo(fromInt(params[1].getClass(), 1)) >= 0){ NumberInterface[] newParams = {params[0], params[1].fractionalPart()}; return params[0].intPow(params[1].floor().intValue()).multiply(applyInternal(newParams)); @@ -476,16 +471,6 @@ public class StandardPlugin extends Plugin { return sum; } - /** - * Returns the maximum error based on the precision of the class of number. - * - * @param number Any instance of the NumberInterface in question (should return an appropriate precision). - * @return the maximum error. - */ - private static NumberInterface getMaxError(NumberInterface number) { - return fromInt(number.getClass(), 10).intPow(-number.getMaxPrecision()); - } - /** * A factorial function that uses memoization for each number class; it efficiently * computes factorials of non-negative integers. @@ -517,7 +502,7 @@ public class StandardPlugin extends Plugin { */ private static NumberInterface sinTaylor(NumberInterface x) { NumberInterface power = x, multiplier = x.multiply(x).negate(), currentTerm = x, sum = x; - NumberInterface maxError = getMaxError(x); + NumberInterface maxError = x.getMaxError(); int n = 1; do { n += 2; diff --git a/src/test/java/org/nwapw/abacus/tests/CalculationTests.java b/src/test/java/org/nwapw/abacus/tests/CalculationTests.java old mode 100644 new mode 100755 index e451732..938b091 --- a/src/test/java/org/nwapw/abacus/tests/CalculationTests.java +++ b/src/test/java/org/nwapw/abacus/tests/CalculationTests.java @@ -88,8 +88,8 @@ public class CalculationTests { public void testExp(){ testOutput("exp0", "exp(0)", "1"); testOutput("exp1", "exp(1)", "2.718281828459045235360287471352662497757247"); - testOutput("exp300", "exp(300)", "19424263952412559365842088360176992193662086"); - testOutput("exp300", "exp(300)", "19424263952412559365842088360176992193662086"); + testOutput("exp300", "exp(300)", "1.9424263952412559365842088360176992193662086"); + testOutput("exp(-500)", "exp((500)`)", "7.1245764067412855315491573771227552469277568"); } @Test @@ -99,6 +99,9 @@ public class CalculationTests { testOutput("2^1", "(2^1)", "2"); testOutput("2^-1", "(2^(1)`)", "0.5"); testOutput("2^50", "(2^50)", "112589990684262"); + testOutput("7^(-sqrt2*17)", "(7^((sqrt(2)*17))`)", "4.81354609155297814551845300063563"); + testEvalError("0^0", "(0^0)"); + testEvalError("(-13)^.9999", "((13)`^0.9999)"); } }