From 11e50eb56f6750d6bdfe15b986eef55e27452211 Mon Sep 17 00:00:00 2001 From: Tec Date: Sat, 18 Jul 2020 22:49:35 +0200 Subject: Implement big float, transition to molarity --- .../java/ch/obermuhlner/math/big/BigComplex.java | 556 ++++++ .../ch/obermuhlner/math/big/BigComplexMath.java | 413 +++++ .../ch/obermuhlner/math/big/BigDecimalMath.java | 1671 +++++++++++++++++ .../java/ch/obermuhlner/math/big/BigFloat.java | 1947 ++++++++++++++++++++ .../java/ch/obermuhlner/math/big/BigRational.java | 1103 +++++++++++ .../math/big/DefaultBigDecimalMath.java | 736 ++++++++ .../math/big/internal/AsinCalculator.java | 46 + .../math/big/internal/AtanhCalculator.java | 40 + .../math/big/internal/CosCalculator.java | 48 + .../math/big/internal/CoshCalculator.java | 43 + .../math/big/internal/ExpCalculator.java | 42 + .../math/big/internal/PowerIterator.java | 28 + .../math/big/internal/PowerNIterator.java | 33 + .../math/big/internal/PowerTwoNIterator.java | 33 + .../big/internal/PowerTwoNMinusOneIterator.java | 35 + .../big/internal/PowerTwoNPlusOneIterator.java | 33 + .../math/big/internal/SeriesCalculator.java | 127 ++ .../math/big/internal/SinCalculator.java | 49 + .../math/big/internal/SinhCalculator.java | 44 + .../math/big/stream/BigDecimalStream.java | 219 +++ .../math/big/stream/BigFloatStream.java | 214 +++ .../tectech/compatibility/gtpp/GtppAtomLoader.java | 24 +- .../core/cElementalInstanceStackMap.java | 18 +- .../core/stacks/cElementalInstanceStack.java | 53 +- .../core/templates/cElementalDefinition.java | 5 + .../core/transformations/bTransformationInfo.java | 5 +- .../definitions/complex/dAtomDefinition.java | 6 +- .../definitions/complex/dHadronDefinition.java | 2 +- .../item/DebugElementalInstanceContainer_EM.java | 8 +- ...GT_MetaTileEntity_Hatch_ElementalContainer.java | 2 +- .../multi/GT_MetaTileEntity_EM_collider.java | 2 +- .../multi/GT_MetaTileEntity_EM_decay.java | 2 +- .../Behaviour_ElectromagneticSeparator.java | 2 +- 33 files changed, 7540 insertions(+), 49 deletions(-) create mode 100644 src/main/java/ch/obermuhlner/math/big/BigComplex.java create mode 100644 src/main/java/ch/obermuhlner/math/big/BigComplexMath.java create mode 100644 src/main/java/ch/obermuhlner/math/big/BigDecimalMath.java create mode 100644 src/main/java/ch/obermuhlner/math/big/BigFloat.java create mode 100644 src/main/java/ch/obermuhlner/math/big/BigRational.java create mode 100644 src/main/java/ch/obermuhlner/math/big/DefaultBigDecimalMath.java create mode 100644 src/main/java/ch/obermuhlner/math/big/internal/AsinCalculator.java create mode 100644 src/main/java/ch/obermuhlner/math/big/internal/AtanhCalculator.java create mode 100644 src/main/java/ch/obermuhlner/math/big/internal/CosCalculator.java create mode 100644 src/main/java/ch/obermuhlner/math/big/internal/CoshCalculator.java create mode 100644 src/main/java/ch/obermuhlner/math/big/internal/ExpCalculator.java create mode 100644 src/main/java/ch/obermuhlner/math/big/internal/PowerIterator.java create mode 100644 src/main/java/ch/obermuhlner/math/big/internal/PowerNIterator.java create mode 100644 src/main/java/ch/obermuhlner/math/big/internal/PowerTwoNIterator.java create mode 100644 src/main/java/ch/obermuhlner/math/big/internal/PowerTwoNMinusOneIterator.java create mode 100644 src/main/java/ch/obermuhlner/math/big/internal/PowerTwoNPlusOneIterator.java create mode 100644 src/main/java/ch/obermuhlner/math/big/internal/SeriesCalculator.java create mode 100644 src/main/java/ch/obermuhlner/math/big/internal/SinCalculator.java create mode 100644 src/main/java/ch/obermuhlner/math/big/internal/SinhCalculator.java create mode 100644 src/main/java/ch/obermuhlner/math/big/stream/BigDecimalStream.java create mode 100644 src/main/java/ch/obermuhlner/math/big/stream/BigFloatStream.java (limited to 'src/main/java') diff --git a/src/main/java/ch/obermuhlner/math/big/BigComplex.java b/src/main/java/ch/obermuhlner/math/big/BigComplex.java new file mode 100644 index 0000000000..a4620ff53b --- /dev/null +++ b/src/main/java/ch/obermuhlner/math/big/BigComplex.java @@ -0,0 +1,556 @@ +package ch.obermuhlner.math.big; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.util.Objects; + +/** + * Represents a complex number consisting of a real and an imaginary {@link BigDecimal} part in the form {@code a + bi}. + * + *

It generally follows the design of {@link BigDecimal} with some convenience improvements like overloaded operator methods.

+ * + *

The biggest difference to {@link BigDecimal} is that {@link BigComplex#equals(Object) BigComplex.equals(Object)} implements the mathematical equality + * and not the strict technical equality. + * This was a difficult decision because it means that {@code BigComplex} behaves slightly different than {@link BigDecimal} + * but considering that the strange equality of {@link BigDecimal} is a major source of bugs we + * decided it was worth the slight inconsistency. + * If you need the strict equality use {@link BigComplex#strictEquals(Object)}`.

+ * + *

This class is immutable and therefore inherently thread safe.

+ */ +public final class BigComplex { + + /** + * Zero represented as complex number. + */ + public static final BigComplex ZERO = new BigComplex(BigDecimal.ZERO, BigDecimal.ZERO); + + /** + * Real 1 represented as complex number. + */ + public static final BigComplex ONE = new BigComplex(BigDecimal.ONE, BigDecimal.ZERO); + + /** + * Imaginary 1 represented as complex number. + */ + public static final BigComplex I = new BigComplex(BigDecimal.ZERO, BigDecimal.ONE); + + /** + * The real {@link BigDecimal} part of this complex number. + */ + public final BigDecimal re; + + /** + * The imaginary {@link BigDecimal} part of this complex number. + */ + public final BigDecimal im; + + private BigComplex(BigDecimal re, BigDecimal im) { + this.re = re; + this.im = im; + } + + /** + * Calculates the addition of the given complex value to this complex number. + * + *

This methods does not modify this instance.

+ * + * @param value the {@link BigComplex} value to add + * @return the calculated {@link BigComplex} result + */ + public BigComplex add(BigComplex value) { + return valueOf( + re.add(value.re), + im.add(value.im)); + } + + /** + * Calculates the addition of the given complex value to this complex number using the specified {@link MathContext}. + * + *

This methods does not modify this instance.

+ * + * @param value the {@link BigComplex} value to add + * @param mathContext the {@link MathContext} used to calculate the result + * @return the calculated {@link BigComplex} result + */ + public BigComplex add(BigComplex value, MathContext mathContext) { + return valueOf( + re.add(value.re, mathContext), + im.add(value.im, mathContext)); + } + + /** + * Calculates the addition of the given real {@link BigDecimal} value to this complex number using the specified {@link MathContext}. + * + *

This methods does not modify this instance.

+ * + * @param value the real {@link BigDecimal} value to add + * @param mathContext the {@link MathContext} used to calculate the result + * @return the calculated {@link BigComplex} result + */ + public BigComplex add(BigDecimal value, MathContext mathContext) { + return valueOf( + re.add(value, mathContext), + im); + } + + /** + * Calculates the addition of the given real {@link BigDecimal} value to this complex number. + * + *

This methods does not modify this instance.

+ * + * @param value the real {@link BigDecimal} value to add + * @return the calculated {@link BigComplex} result + */ + public BigComplex add(BigDecimal value) { + return valueOf( + re.add(value), + im); + } + + /** + * Calculates the addition of the given real {@code double} value to this complex number. + * + *

This methods does not modify this instance.

+ * + * @param value the real {@code double} value to add + * @return the calculated {@link BigComplex} result + */ + public BigComplex add(double value) { + return add(BigDecimal.valueOf(value)); + } + + /** + * Calculates the subtraction of the given complex value from this complex number. + * + *

This methods does not modify this instance.

+ * + * @param value the {@link BigComplex} value to subtract + * @return the calculated {@link BigComplex} result + */ + public BigComplex subtract(BigComplex value) { + return valueOf( + re.subtract(value.re), + im.subtract(value.im)); + } + + /** + * Calculates the subtraction of the given complex value from this complex number using the specified {@link MathContext}. + * + *

This methods does not modify this instance.

+ * + * @param value the {@link BigComplex} value to subtract + * @param mathContext the {@link MathContext} used to calculate the result + * @return the calculated {@link BigComplex} result + */ + public BigComplex subtract(BigComplex value, MathContext mathContext) { + return valueOf( + re.subtract(value.re, mathContext), + im.subtract(value.im, mathContext)); + } + + /** + * Calculates the subtraction of the given real {@link BigDecimal} value from this complex number using the specified {@link MathContext}. + * + *

This methods does not modify this instance.

+ * + * @param value the real {@link BigDecimal} value to add + * @param mathContext the {@link MathContext} used to calculate the result + * @return the calculated {@link BigComplex} result + */ + public BigComplex subtract(BigDecimal value, MathContext mathContext) { + return valueOf( + re.subtract(value, mathContext), + im); + } + + /** + * Calculates the subtraction of the given real {@link BigDecimal} value from this complex number. + * + *

This methods does not modify this instance.

+ * + * @param value the real {@link BigDecimal} value to subtract + * @return the calculated {@link BigComplex} result + */ + public BigComplex subtract(BigDecimal value) { + return valueOf( + re.subtract(value), + im); + } + + /** + * Calculates the subtraction of the given real {@code double} value from this complex number. + * + *

This methods does not modify this instance.

+ * + * @param value the real {@code double} value to subtract + * @return the calculated {@link BigComplex} result + */ + public BigComplex subtract(double value) { + return subtract(BigDecimal.valueOf(value)); + } + + /** + * Calculates the multiplication of the given complex value to this complex number. + * + *

This methods does not modify this instance.

+ * + * @param value the {@link BigComplex} value to multiply + * @return the calculated {@link BigComplex} result + */ + public BigComplex multiply(BigComplex value) { + return valueOf( + re.multiply(value.re).subtract(im.multiply(value.im)), + re.multiply(value.im).add(im.multiply(value.re))); + } + + /** + * Calculates the multiplication of the given complex value with this complex number using the specified {@link MathContext}. + * + *

This methods does not modify this instance.

+ * + * @param value the {@link BigComplex} value to multiply + * @param mathContext the {@link MathContext} used to calculate the result + * @return the calculated {@link BigComplex} result + */ + public BigComplex multiply(BigComplex value, MathContext mathContext) { + return valueOf( + re.multiply(value.re, mathContext).subtract(im.multiply(value.im, mathContext), mathContext), + re.multiply(value.im, mathContext).add(im.multiply(value.re, mathContext), mathContext)); + } + + /** + * Calculates the multiplication of the given real {@link BigDecimal} value with this complex number using the specified {@link MathContext}. + * + *

This methods does not modify this instance.

+ * + * @param value the real {@link BigDecimal} value to multiply + * @param mathContext the {@link MathContext} used to calculate the result + * @return the calculated {@link BigComplex} result + */ + public BigComplex multiply(BigDecimal value, MathContext mathContext) { + return valueOf( + re.multiply(value, mathContext), + im.multiply(value, mathContext)); + } + + /** + * Calculates the multiplication of the given real {@link BigDecimal} value with this complex number. + * + *

This methods does not modify this instance.

+ * + * @param value the real {@link BigDecimal} value to multiply + * @return the calculated {@link BigComplex} result + */ + public BigComplex multiply(BigDecimal value) { + return valueOf( + re.multiply(value), + im.multiply(value)); + } + + /** + * Calculates the multiplication of the given real {@code double} value with this complex number. + * + *

This methods does not modify this instance.

+ * + * @param value the real {@code double} value to multiply + * @return the calculated {@link BigComplex} result + */ + public BigComplex multiply(double value) { + return multiply(BigDecimal.valueOf(value)); + } + + /** + * Calculates this complex number divided by the given complex value using the specified {@link MathContext}. + * + *

This methods does not modify this instance.

+ * + * @param value the {@link BigComplex} value to divide by + * @param mathContext the {@link MathContext} used to calculate the result + * @return the calculated {@link BigComplex} result + */ + public BigComplex divide(BigComplex value, MathContext mathContext) { + return multiply(value.reciprocal(mathContext), mathContext); + } + + /** + * Calculates this complex number divided by the given real {@link BigDecimal} value using the specified {@link MathContext}. + * + *

This methods does not modify this instance.

+ * + * @param value the {@link BigDecimal} value to divide by + * @param mathContext the {@link MathContext} used to calculate the result + * @return the calculated {@link BigComplex} result + */ + public BigComplex divide(BigDecimal value, MathContext mathContext) { + return valueOf( + re.divide(value, mathContext), + im.divide(value, mathContext)); + } + + /** + * Calculates this complex number divided by the given real {@code double} value using the specified {@link MathContext}. + * + *

This methods does not modify this instance.

+ * + * @param value the {@code double} value to divide by + * @param mathContext the {@link MathContext} used to calculate the result + * @return the calculated {@link BigComplex} result + */ + public BigComplex divide(double value, MathContext mathContext) { + return divide(BigDecimal.valueOf(value), mathContext); + } + + /** + * Calculates the reciprocal of this complex number using the specified {@link MathContext}. + * + *

This methods does not modify this instance.

+ * + * @param mathContext the {@link MathContext} used to calculate the result + * @return the calculated {@link BigComplex} result + */ + public BigComplex reciprocal(MathContext mathContext) { + BigDecimal scale = absSquare(mathContext); + return valueOf( + re.divide(scale, mathContext), + im.negate().divide(scale, mathContext)); + } + + /** + * Calculates the conjugate {@code a - bi} of this complex number. + * + *

This methods does not modify this instance.

+ * + * @return the calculated {@link BigComplex} result + */ + public BigComplex conjugate() { + return valueOf(re, im.negate()); + } + + /** + * Calculates the negation {@code -a - bi} of this complex number. + * + *

This methods does not modify this instance.

+ * + * @return the calculated {@link BigComplex} result + */ + public BigComplex negate() { + return valueOf(re.negate(), im.negate()); + } + + /** + * Calculates the absolute value (also known as magnitude, length or radius) of this complex number. + * + *

This method is slower than {@link #absSquare(MathContext)} since it needs to calculate the {@link BigDecimalMath#sqrt(BigDecimal, MathContext)}.

+ * + *

This methods does not modify this instance.

+ * + * @param mathContext the {@link MathContext} used to calculate the result + * @return the calculated {@link BigComplex} result + * @see #absSquare(MathContext) + */ + public BigDecimal abs(MathContext mathContext) { + return BigDecimalMath.sqrt(absSquare(mathContext), mathContext); + } + + /** + * Calculates the angle in radians (also known as argument) of this complex number. + * + *

This methods does not modify this instance.

+ * + * @param mathContext the {@link MathContext} used to calculate the result + * @return the calculated {@link BigComplex} result + */ + public BigDecimal angle(MathContext mathContext) { + return BigDecimalMath.atan2(im, re, mathContext); + } + + /** + * Calculates the square of the absolute value of this complex number. + * + *

This method is faster than {@link #abs(MathContext)} since it does not need to calculate the {@link BigDecimalMath#sqrt(BigDecimal, MathContext)}.

+ * + *

This methods does not modify this instance.

+ * + * @param mathContext the {@link MathContext} used to calculate the result + * @return the calculated {@link BigComplex} result + * @see #abs(MathContext) + */ + public BigDecimal absSquare(MathContext mathContext) { + return re.multiply(re, mathContext).add(im.multiply(im, mathContext), mathContext); + } + + /** + * Returns whether this complex number only has a real part (the imaginary part is 0). + * + * @return {@code true} if this complex number only has a real part, {@code false} if the imaginary part is not 0 + */ + public boolean isReal() { + return im.signum() == 0; + } + + /** + * Returns the real part of this complex number as {@link BigComplex} number. + * + * @return the real part as as {@link BigComplex} number + */ + public BigComplex re() { + return valueOf(re, BigDecimal.ZERO); + } + + /** + * Returns the imaginary part of this complex number as {@link BigComplex} number. + * + * @return the imaginary part as as {@link BigComplex} number + */ + public BigComplex im() { + return valueOf(BigDecimal.ZERO, im); + } + + /** + * Returns this complex nuber rounded to the specified precision. + * + *

This methods does not modify this instance.

+ * + * @param mathContext the {@link MathContext} used to calculate the result + * @return the rounded {@link BigComplex} result + */ + public BigComplex round(MathContext mathContext) { + return valueOf(re.round(mathContext), im.round(mathContext)); + } + + @Override + public int hashCode() { + return Objects.hash(re, im); + } + + /** + * {@inheritDoc} + * + *

Contrary to {@link BigDecimal#equals(Object)} this method implements mathematical equality + * (by calling {@link BigDecimal#compareTo(BigDecimal)} on the real and imaginary parts) + * instead of strict equality.

+ * + * @see #strictEquals(Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + BigComplex other = (BigComplex) obj; + + return re.compareTo(other.re) == 0 && im.compareTo(other.im) == 0; + } + + /** + * Returns whether the real and imaginary parts of this complex number are strictly equal. + * + *

This method uses the strict equality as defined by {@link BigDecimal#equals(Object)} on the real and imaginary parts.

+ *

Please note that {@link #equals(Object) BigComplex.equals(Object)} implements mathematical equality instead + * (by calling {@link BigDecimal#compareTo(BigDecimal) on the real and imaginary parts}).

+ * + * @param obj the object to compare for strict equality + * @return {@code true} if the specified object is strictly equal to this complex number + * @see #equals(Object) + */ + public boolean strictEquals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + BigComplex other = (BigComplex) obj; + + return re.equals(other.re) && im.equals(other.im); + } + + @Override + public String toString() { + if (im.signum() >= 0) { + return "(" + re + " + " + im + " i)"; + } else { + return "(" + re + " - " + im.negate() + " i)"; + } + } + + /** + * Returns a complex number with the specified real {@link BigDecimal} part. + * + * @param real the real {@link BigDecimal} part + * @return the complex number + */ + public static BigComplex valueOf(BigDecimal real) { + return valueOf(real, BigDecimal.ZERO); + } + + /** + * Returns a complex number with the specified real {@code double} part. + * + * @param real the real {@code double} part + * @return the complex number + */ + public static BigComplex valueOf(double real) { + return valueOf(BigDecimal.valueOf(real), BigDecimal.ZERO); + } + + /** + * Returns a complex number with the specified real and imaginary {@code double} parts. + * + * @param real the real {@code double} part + * @param imaginary the imaginary {@code double} part + * @return the complex number + */ + public static BigComplex valueOf(double real, double imaginary) { + return valueOf(BigDecimal.valueOf(real), BigDecimal.valueOf(imaginary)); + } + + /** + * Returns a complex number with the specified real and imaginary {@link BigDecimal} parts. + * + * @param real the real {@link BigDecimal} part + * @param imaginary the imaginary {@link BigDecimal} part + * @return the complex number + */ + public static BigComplex valueOf(BigDecimal real, BigDecimal imaginary) { + if (real.signum() == 0) { + if (imaginary.signum() == 0) { + return ZERO; + } + if (imaginary.compareTo(BigDecimal.ONE) == 0) { + return I; + } + } + if (imaginary.signum() == 0 && real.compareTo(BigDecimal.ONE) == 0) { + return ONE; + } + + return new BigComplex(real, imaginary); + } + + /** + * Returns a complex number with the specified polar {@link BigDecimal} radius and angle using the specified {@link MathContext}. + * + * @param radius the {@link BigDecimal} radius of the polar representation + * @param angle the {@link BigDecimal} angle in radians of the polar representation + * @param mathContext the {@link MathContext} used to calculate the result + * @return the complex number + */ + public static BigComplex valueOfPolar(BigDecimal radius, BigDecimal angle, MathContext mathContext) { + if (radius.signum() == 0) { + return ZERO; + } + + return valueOf( + radius.multiply(BigDecimalMath.cos(angle, mathContext), mathContext), + radius.multiply(BigDecimalMath.sin(angle, mathContext), mathContext)); + } + + public static BigComplex valueOfPolar(double radius, double angle, MathContext mathContext) { + return valueOfPolar(BigDecimal.valueOf(radius), BigDecimal.valueOf(angle), mathContext); + } +} diff --git a/src/main/java/ch/obermuhlner/math/big/BigComplexMath.java b/src/main/java/ch/obermuhlner/math/big/BigComplexMath.java new file mode 100644 index 0000000000..a73d9bccdd --- /dev/null +++ b/src/main/java/ch/obermuhlner/math/big/BigComplexMath.java @@ -0,0 +1,413 @@ +package ch.obermuhlner.math.big; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.util.List; + +import static ch.obermuhlner.math.big.BigComplex.I; + +/** + * Provides advanced functions operating on {@link BigComplex}s. + */ +public class BigComplexMath { + + private static final BigDecimal TWO = BigDecimal.valueOf(2); + + /** + * Calculates the reciprocal of the given complex number using the specified {@link MathContext}. + * + * @param x the complex number to calculate the reciprocal + * @param mathContext the {@link MathContext} used to calculate the result + * @return the calculated {@link BigComplex} result + * @see BigComplex#reciprocal(MathContext) + */ + public static BigComplex reciprocal(BigComplex x, MathContext mathContext) { + return x.reciprocal(mathContext); + } + + /** + * Calculates the conjugate of the given complex number using the specified {@link MathContext}. + * + * @param x the complex number to calculate the conjugate + * @return the calculated {@link BigComplex} result + * @see BigComplex#conjugate() + */ + public static BigComplex conjugate(BigComplex x) { + return x.conjugate(); + } + + /** + * Calculates the absolute value (also known as magnitude, length or radius) of the given complex number using the specified {@link MathContext}. + * + * @param x the complex number to calculate the absolute value + * @param mathContext the {@link MathContext} used to calculate the result + * @return the calculated {@link BigComplex} result + * @see BigComplex#abs(MathContext) + */ + public static BigDecimal abs(BigComplex x, MathContext mathContext) { + return x.abs(mathContext); + } + + /** + * Calculates the square of the absolute value (also known as magnitude, length or radius) of the given complex number using the specified {@link MathContext}. + * + * @param x the complex number to calculate the square of the absolute value + * @param mathContext the {@link MathContext} used to calculate the result + * @return the calculated {@link BigComplex} result + * @see BigComplex#absSquare(MathContext) + */ + public static BigDecimal absSquare(BigComplex x, MathContext mathContext) { + return x.absSquare(mathContext); + } + + /** + * Calculates the angle in radians of the given complex number using the specified {@link MathContext}. + * + * @param x the complex number to calculate the angle + * @param mathContext the {@link MathContext} used to calculate the result + * @return the calculated {@link BigComplex} angle in radians + * @see BigComplex#angle(MathContext) + */ + public static BigDecimal angle(BigComplex x, MathContext mathContext) { + return x.angle(mathContext); + } + + /** + * Calculates the factorial of the specified {@link BigComplex}. + * + *

This implementation uses + * Spouge's approximation + * to calculate the factorial for non-integer values.

+ * + *

This involves calculating a series of constants that depend on the desired precision. + * Since this constant calculation is quite expensive (especially for higher precisions), + * the constants for a specific precision will be cached + * and subsequent calls to this method with the same precision will be much faster.

+ * + *

It is therefore recommended to do one call to this method with the standard precision of your application during the startup phase + * and to avoid calling it with many different precisions.

+ * + *

See: Wikipedia: Factorial - Extension of factorial to non-integer values of argument

+ * + * @param x the {@link BigComplex} + * @param mathContext the {@link MathContext} used for the result + * @return the factorial {@link BigComplex} + * @throws ArithmeticException if x is a negative integer value (-1, -2, -3, ...) + * @see BigDecimalMath#factorial(BigDecimal, MathContext) + * @see #gamma(BigComplex, MathContext) + */ + public static BigComplex factorial(BigComplex x, MathContext mathContext) { + if (x.isReal() && BigDecimalMath.isIntValue(x.re)) { + return BigComplex.valueOf(BigDecimalMath.factorial(x.re.intValueExact()).round(mathContext)); + } + + // https://en.wikipedia.org/wiki/Spouge%27s_approximation + MathContext mc = new MathContext(mathContext.getPrecision() * 2, mathContext.getRoundingMode()); + + int a = mathContext.getPrecision() * 13 / 10; + List constants = BigDecimalMath.getSpougeFactorialConstants(a); + + BigDecimal bigA = BigDecimal.valueOf(a); + + boolean negative = false; + BigComplex factor = BigComplex.valueOf(constants.get(0)); + for (int k = 1; k < a; k++) { + BigDecimal bigK = BigDecimal.valueOf(k); + factor = factor.add(BigComplex.valueOf(constants.get(k)).divide(x.add(bigK), mc), mc); + negative = !negative; + } + + BigComplex result = pow(x.add(bigA, mc), x.add(BigDecimal.valueOf(0.5), mc), mc); + result = result.multiply(exp(x.negate().subtract(bigA, mc), mc), mc); + result = result.multiply(factor, mc); + + return result.round(mathContext); + } + + /** + * Calculates the gamma function of the specified {@link BigComplex}. + * + *

This implementation uses {@link #factorial(BigComplex, MathContext)} internally, + * therefore the performance implications described there apply also for this method. + * + *

See: Wikipedia: Gamma function

+ * + * @param x the {@link BigComplex} + * @param mathContext the {@link MathContext} used for the result + * @return the gamma {@link BigComplex} + * @throws ArithmeticException if x-1 is a negative integer value (-1, -2, -3, ...) + * @see BigDecimalMath#gamma(BigDecimal, MathContext) + * @see #factorial(BigComplex, MathContext) + */ + public static BigComplex gamma(BigComplex x, MathContext mathContext) { + return factorial(x.subtract(BigComplex.ONE), mathContext); + } + + + /** + * Calculates the natural exponent of {@link BigComplex} x (ex) in the complex domain. + * + *

See: Wikipedia: Exponent (Complex plane)

+ * + * @param x the {@link BigComplex} to calculate the exponent for + * @param mathContext the {@link MathContext} used for the result + * @return the calculated exponent {@link BigComplex} with the precision specified in the mathContext + */ + public static BigComplex exp(BigComplex x, MathContext mathContext) { + MathContext mc = new MathContext(mathContext.getPrecision() + 4, mathContext.getRoundingMode()); + + BigDecimal expRe = BigDecimalMath.exp(x.re, mc); + return BigComplex.valueOf( + expRe.multiply(BigDecimalMath.cos(x.im, mc), mc).round(mathContext), + expRe.multiply(BigDecimalMath.sin(x.im, mc), mc)).round(mathContext); + } + + /** + * Calculates the sine (sinus) of {@link BigComplex} x in the complex domain. + * + *

See: Wikipedia: Sine (Sine with a complex argument)

+ * + * @param x the {@link BigComplex} to calculate the sine for + * @param mathContext the {@link MathContext} used for the result + * @return the calculated sine {@link BigComplex} with the precision specified in the mathContext + */ + public static BigComplex sin(BigComplex x, MathContext mathContext) { + MathContext mc = new MathContext(mathContext.getPrecision() + 4, mathContext.getRoundingMode()); + + return BigComplex.valueOf( + BigDecimalMath.sin(x.re, mc).multiply(BigDecimalMath.cosh(x.im, mc), mc).round(mathContext), + BigDecimalMath.cos(x.re, mc).multiply(BigDecimalMath.sinh(x.im, mc), mc).round(mathContext)); + } + + /** + * Calculates the cosine (cosinus) of {@link BigComplex} x in the complex domain. + * + * @param x the {@link BigComplex} to calculate the cosine for + * @param mathContext the {@link MathContext} used for the result + * @return the calculated cosine {@link BigComplex} with the precision specified in the mathContext + */ + public static BigComplex cos(BigComplex x, MathContext mathContext) { + MathContext mc = new MathContext(mathContext.getPrecision() + 4, mathContext.getRoundingMode()); + + return BigComplex.valueOf( + BigDecimalMath.cos(x.re, mc).multiply(BigDecimalMath.cosh(x.im, mc), mc).round(mathContext), + BigDecimalMath.sin(x.re, mc).multiply(BigDecimalMath.sinh(x.im, mc), mc).negate().round(mathContext)); + } + + // + // http://scipp.ucsc.edu/~haber/archives/physics116A10/arc_10.pdf + + /** + * Calculates the tangens of {@link BigComplex} x in the complex domain. + * + * @param x the {@link BigComplex} to calculate the tangens for + * @param mathContext the {@link MathContext} used for the result + * @return the calculated tangens {@link BigComplex} with the precision specified in the mathContext + */ + public static BigComplex tan(BigComplex x, MathContext mathContext) { + MathContext mc = new MathContext(mathContext.getPrecision() + 4, mathContext.getRoundingMode()); + + return sin(x, mc).divide(cos(x, mc), mc).round(mathContext); + } + + /** + * Calculates the arc tangens (inverted tangens) of {@link BigComplex} x in the complex domain. + * + *

See: Wikipedia: Inverse trigonometric functions (Extension to complex plane)

+ * + * @param x the {@link BigComplex} to calculate the arc tangens for + * @param mathContext the {@link MathContext} used for the result + * @return the calculated arc tangens {@link BigComplex} with the precision specified in the mathContext + */ + public static BigComplex atan(BigComplex x, MathContext mathContext) { + MathContext mc = new MathContext(mathContext.getPrecision() + 4, mathContext.getRoundingMode()); + + return log(I.subtract(x, mc).divide(I.add(x, mc), mc), mc).divide(I, mc).divide(TWO, mc).round(mathContext); + } + + /** + * Calculates the arc cotangens (inverted cotangens) of {@link BigComplex} x in the complex domain. + * + *

See: Wikipedia: Inverse trigonometric functions (Extension to complex plane)

+ * + * @param x the {@link BigComplex} to calculate the arc cotangens for + * @param mathContext the {@link MathContext} used for the result + * @return the calculated arc cotangens {@link BigComplex} with the precision specified in the mathContext + */ + public static BigComplex acot(BigComplex x, MathContext mathContext) { + MathContext mc = new MathContext(mathContext.getPrecision() + 4, mathContext.getRoundingMode()); + + return log(x.add(I, mc).divide(x.subtract(I, mc), mc), mc).divide(I, mc).divide(TWO, mc).round(mathContext); + } + + /** + * Calculates the arc sine (inverted sine) of {@link BigComplex} x in the complex domain. + * + *

See: Wikipedia: Inverse trigonometric functions (Extension to complex plane)

+ * + * @param x the {@link BigComplex} to calculate the arc sine for + * @param mathContext the {@link MathContext} used for the result + * @return the calculated arc sine {@link BigComplex} with the precision specified in the mathContext + */ + public static BigComplex asin(BigComplex x, MathContext mathContext) { + MathContext mc = new MathContext(mathContext.getPrecision() + 4, mathContext.getRoundingMode()); + + return I.negate().multiply(log(I.multiply(x, mc).add(sqrt(BigComplex.ONE.subtract(x.multiply(x, mc), mc), mc), mc), mc), mc).round(mathContext); + } + + /** + * Calculates the arc cosine (inverted cosine) of {@link BigComplex} x in the complex domain. + * + *

See: Wikipedia: Inverse trigonometric functions (Extension to complex plane)

+ * + * @param x the {@link BigComplex} to calculate the arc cosine for + * @param mathContext the {@link MathContext} used for the result + * @return the calculated arc cosine {@link BigComplex} with the precision specified in the mathContext + */ + public static BigComplex acos(BigComplex x, MathContext mathContext) { + MathContext mc = new MathContext(mathContext.getPrecision() + 4, mathContext.getRoundingMode()); + + return I.negate().multiply(log(x.add(sqrt(x.multiply(x, mc).subtract(BigComplex.ONE, mc), mc), mc), mc), mc).round(mathContext); + } + + /** + * Calculates the square root of {@link BigComplex} x in the complex domain (sqrt x). + * + *

See Wikipedia: Square root (Square root of an imaginary number)

+ * + * @param x the {@link BigComplex} to calculate the square root for + * @param mathContext the {@link MathContext} used for the result + * @return the calculated square root {@link BigComplex} with the precision specified in the mathContext + */ + public static BigComplex sqrt(BigComplex x, MathContext mathContext) { + // https://math.stackexchange.com/questions/44406/how-do-i-get-the-square-root-of-a-complex-number + MathContext mc = new MathContext(mathContext.getPrecision() + 4, mathContext.getRoundingMode()); + + BigDecimal magnitude = x.abs(mc); + + BigComplex a = x.add(magnitude, mc); + return a.divide(a.abs(mc), mc).multiply(BigDecimalMath.sqrt(magnitude, mc), mc).round(mathContext); + } + + /** + * Calculates the natural logarithm of {@link BigComplex} x in the complex domain. + * + *

See: Wikipedia: Complex logarithm

+ * + * @param x the {@link BigComplex} to calculate the natural logarithm for + * @param mathContext the {@link MathContext} used for the result + * @return the calculated natural logarithm {@link BigComplex} with the precision specified in the mathContext + */ + public static BigComplex log(BigComplex x, MathContext mathContext) { + // https://en.wikipedia.org/wiki/Complex_logarithm + MathContext mc1 = new MathContext(mathContext.getPrecision() + 20, mathContext.getRoundingMode()); + MathContext mc2 = new MathContext(mathContext.getPrecision() + 5, mathContext.getRoundingMode()); + + return BigComplex.valueOf( + BigDecimalMath.log(x.abs(mc1), mc1).round(mathContext), + x.angle(mc2)).round(mathContext); + } + + /** + * Calculates {@link BigComplex} x to the power of long y (xy). + * + *

The implementation tries to minimize the number of multiplications of {@link BigComplex x} (using squares whenever possible).

+ * + *

See: Wikipedia: Exponentiation - efficient computation

+ * + * @param x the {@link BigComplex} value to take to the power + * @param y the long value to serve as exponent + * @param mathContext the {@link MathContext} used for the result + * @return the calculated x to the power of y with the precision specified in the mathContext + */ + public static BigComplex pow(BigComplex x, long y, MathContext mathContext) { + MathContext mc = new MathContext(mathContext.getPrecision() + 10, mathContext.getRoundingMode()); + + if (y < 0) { + return BigComplex.ONE.divide(pow(x, -y, mc), mc).round(mathContext); + } + + BigComplex result = BigComplex.ONE; + while (y > 0) { + if ((y & 1) == 1) { + // odd exponent -> multiply result with x + result = result.multiply(x, mc); + y -= 1; + } + + if (y > 0) { + // even exponent -> square x + x = x.multiply(x, mc); + } + + y >>= 1; + } + + return result.round(mathContext); + } + + /** + * Calculates {@link BigComplex} x to the power of {@link BigDecimal} y (xy). + * + * @param x the {@link BigComplex} value to take to the power + * @param y the {@link BigDecimal} value to serve as exponent + * @param mathContext the {@link MathContext} used for the result + * @return the calculated x to the power of y with the precision specified in the mathContext + */ + public static BigComplex pow(BigComplex x, BigDecimal y, MathContext mathContext) { + MathContext mc = new MathContext(mathContext.getPrecision() + 4, mathContext.getRoundingMode()); + + BigDecimal angleTimesN = x.angle(mc).multiply(y, mc); + return BigComplex.valueOf( + BigDecimalMath.cos(angleTimesN, mc), + BigDecimalMath.sin(angleTimesN, mc)).multiply(BigDecimalMath.pow(x.abs(mc), y, mc), mc).round(mathContext); + } + + /** + * Calculates {@link BigComplex} x to the power of {@link BigComplex} y (xy). + * + * @param x the {@link BigComplex} value to take to the power + * @param y the {@link BigComplex} value to serve as exponent + * @param mathContext the {@link MathContext} used for the result + * @return the calculated x to the power of y with the precision specified in the mathContext + */ + public static BigComplex pow(BigComplex x, BigComplex y, MathContext mathContext) { + MathContext mc = new MathContext(mathContext.getPrecision() + 4, mathContext.getRoundingMode()); + + return exp(y.multiply(log(x, mc), mc), mc).round(mathContext); + } + + /** + * Calculates the {@link BigDecimal} n'th root of {@link BigComplex} x (nsqrt x). + * + *

See Wikipedia: Square root

+ * @param x the {@link BigComplex} value to calculate the n'th root + * @param n the {@link BigDecimal} defining the root + * @param mathContext the {@link MathContext} used for the result + * + * @return the calculated n'th root of x with the precision specified in the mathContext + */ + public static BigComplex root(BigComplex x, BigDecimal n, MathContext mathContext) { + MathContext mc = new MathContext(mathContext.getPrecision() + 4, mathContext.getRoundingMode()); + + return pow(x, BigDecimal.ONE.divide(n, mc), mc).round(mathContext); + } + + /** + * Calculates the {@link BigComplex} n'th root of {@link BigComplex} x (nsqrt x). + * + *

See Wikipedia: Square root

+ * @param x the {@link BigComplex} value to calculate the n'th root + * @param n the {@link BigComplex} defining the root + * @param mathContext the {@link MathContext} used for the result + * + * @return the calculated n'th root of x with the precision specified in the mathContext + */ + public static BigComplex root(BigComplex x, BigComplex n, MathContext mathContext) { + MathContext mc = new MathContext(mathContext.getPrecision() + 4, mathContext.getRoundingMode()); + + return pow(x, BigComplex.ONE.divide(n, mc), mc).round(mathContext); + } + + // TODO add root() for the k'th root - https://math.stackexchange.com/questions/322481/principal-nth-root-of-a-complex-number +} diff --git a/src/main/java/ch/obermuhlner/math/big/BigDecimalMath.java b/src/main/java/ch/obermuhlner/math/big/BigDecimalMath.java new file mode 100644 index 0000000000..552331f3b4 --- /dev/null +++ b/src/main/java/ch/obermuhlner/math/big/BigDecimalMath.java @@ -0,0 +1,1671 @@ +package ch.obermuhlner.math.big; + +import static java.math.BigDecimal.ONE; +import static java.math.BigDecimal.TEN; +import static java.math.BigDecimal.ZERO; +import static java.math.BigDecimal.valueOf; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.util.*; + +import ch.obermuhlner.math.big.internal.AsinCalculator; +import ch.obermuhlner.math.big.internal.CosCalculator; +import ch.obermuhlner.math.big.internal.CoshCalculator; +import ch.obermuhlner.math.big.internal.ExpCalculator; +import ch.obermuhlner.math.big.internal.SinCalculator; +import ch.obermuhlner.math.big.internal.SinhCalculator; + +/** + * Provides advanced functions operating on {@link BigDecimal}s. + */ +public class BigDecimalMath { + + private static final BigDecimal TWO = valueOf(2); + private static final BigDecimal THREE = valueOf(3); + private static final BigDecimal MINUS_ONE = valueOf(-1); + private static final BigDecimal ONE_HALF = valueOf(0.5); + + private static final BigDecimal DOUBLE_MAX_VALUE = BigDecimal.valueOf(Double.MAX_VALUE); + + private static volatile BigDecimal log2Cache; + private static final Object log2CacheLock = new Object(); + + private static volatile BigDecimal log3Cache; + private static final Object log3CacheLock = new Object(); + + private static volatile BigDecimal log10Cache; + private static final Object log10CacheLock = new Object(); + + private static volatile BigDecimal piCache; + private static final Object piCacheLock = new Object(); + + private static volatile BigDecimal eCache; + private static final Object eCacheLock = new Object(); + + private static final BigDecimal ROUGHLY_TWO_PI = new BigDecimal("3.141592653589793").multiply(TWO); + + private static final int EXPECTED_INITIAL_PRECISION = 15; + + private static BigDecimal[] factorialCache = new BigDecimal[100]; + + static { + BigDecimal result = ONE; + factorialCache[0] = result; + for (int i = 1; i < factorialCache.length; i++) { + result = result.multiply(valueOf(i)); + factorialCache[i] = result; + } + } + + private static final Map> spougeFactorialConstantsCache = new HashMap<>(); + private static final Object spougeFactorialConstantsCacheLock = new Object(); + + private BigDecimalMath() { + // prevent instances + } + + /** + * Creates a {@link BigDecimal} from the specified String representation. + * + *

This method is equivalent to the String constructor {@link BigDecimal#BigDecimal(String)} + * but has been optimized for large strings (several thousand digits).

+ * + * @param string the String representation + * @return the created {@link BigDecimal} + * @throws NumberFormatException if string is not a valid representation of a {@link BigDecimal} + * @see BigDecimal#BigDecimal(String) + * @see #toBigDecimal(String, MathContext) + */ + public static BigDecimal toBigDecimal(String string) { + return toBigDecimal(string, MathContext.UNLIMITED); + } + + /** + * Creates a {@link BigDecimal} from the specified String representation. + * + *

This method is equivalent to the String constructor {@link BigDecimal#BigDecimal(String, MathContext)} + * but has been optimized for large strings (several thousand digits).

+ * + * @param string the string representation + * @param mathContext the {@link MathContext} used for the result + * @return the created {@link BigDecimal} + * @throws NumberFormatException if string is not a valid representation of a {@link BigDecimal} + * @throws ArithmeticException if the result is inexact but the rounding mode is {@code UNNECESSARY} + * @see BigDecimal#BigDecimal(String, MathContext) + * @see #toBigDecimal(String) + */ + public static BigDecimal toBigDecimal(String string, MathContext mathContext) { + int len = string.length(); + if (len < 600) { + return new BigDecimal(string, mathContext); + } + + int splitLength = len / (len >= 10000 ? 8 : 5); + return toBigDecimal(string, mathContext, splitLength); + } + + static BigDecimal toBigDecimal(String string, MathContext mathContext, int splitLength) { + int len = string.length(); + + if (len < splitLength) { + return new BigDecimal(string, mathContext); + } + + char[] chars = string.toCharArray(); + + boolean numberHasSign = false; + boolean negative = false; + int numberIndex = 0; + int dotIndex = -1; + int expIndex = -1; + boolean expHasSign = false; + int scale = 0; + + for (int i = 0; i < len; i++) { + char c = chars[i]; + switch (c) { + case '+': + if (expIndex >= 0) { + if (expHasSign) { + throw new NumberFormatException("Multiple signs in exponent"); + } + expHasSign = true; + } else { + if (numberHasSign) { + throw new NumberFormatException("Multiple signs in number"); + } + numberHasSign = true; + numberIndex = i + 1; + } + break; + case '-': + if (expIndex >= 0) { + if (expHasSign) { + throw new NumberFormatException("Multiple signs in exponent"); + } + expHasSign = true; + } else { + if (numberHasSign) { + throw new NumberFormatException("Multiple signs in number"); + } + numberHasSign = true; + negative = true; + numberIndex = i + 1; + } + break; + case 'e': + case 'E': + if (expIndex >= 0) { + throw new NumberFormatException("Multiple exponent markers"); + } + expIndex = i; + break; + case '.': + if (dotIndex >= 0) { + throw new NumberFormatException("Multiple decimal points"); + } + dotIndex = i; + break; + default: + if (dotIndex >= 0 && expIndex == -1) { + scale++; + } + } + } + + int numberEndIndex; + int exp = 0; + if (expIndex >= 0) { + numberEndIndex = expIndex; + String expString = new String(chars, expIndex + 1, len - expIndex - 1); + exp = Integer.parseInt(expString); + scale = adjustScale(scale, exp); + } else { + numberEndIndex = len; + } + + BigDecimal result; + + if (dotIndex >= 0) { + int leftLength = dotIndex - numberIndex; + BigDecimal bigDecimalLeft = toBigDecimalRecursive(chars, numberIndex, leftLength, exp, splitLength); + int rightLength = numberEndIndex - dotIndex - 1; + BigDecimal bigDecimalRight = toBigDecimalRecursive(chars, dotIndex + 1, rightLength, exp-rightLength, splitLength); + result = bigDecimalLeft.add(bigDecimalRight); + } else { + result = toBigDecimalRecursive(chars, numberIndex, numberEndIndex - numberIndex, exp, splitLength); + } + + if (scale != 0) { + result = result.setScale(scale); + } + + if (negative) { + result = result.negate(); + } + + if (mathContext.getPrecision() != 0) { + result = result.round(mathContext); + } + + return result; + } + + private static int adjustScale(int scale, long exp) { + long adjustedScale = scale - exp; + if (adjustedScale > Integer.MAX_VALUE || adjustedScale < Integer.MIN_VALUE) + throw new NumberFormatException("Scale out of range: " + adjustedScale + " while adjusting scale " + scale + " to exponent " + exp); + return (int) adjustedScale; + } + + private static BigDecimal toBigDecimalRecursive(char[] chars, int offset, int length, int scale, int splitLength) { + if (length > splitLength) { + int mid = length / 2; + BigDecimal bigDecimalLeft = toBigDecimalRecursive(chars, offset, mid, scale + length - mid, splitLength); + BigDecimal bigDecimalRight = toBigDecimalRecursive(chars, offset + mid, length - mid, scale, splitLength); + return bigDecimalLeft.add(bigDecimalRight); + } + if (length == 0) { + return BigDecimal.ZERO; + } + return new BigDecimal(chars, offset, length).movePointRight(scale); + } + + /** + * Returns whether the specified {@link BigDecimal} value can be represented as int. + * + *

If this returns true you can call {@link BigDecimal#intValueExact()} without fear of an {@link ArithmeticException}.

+ * + * @param value the {@link BigDecimal} to check + * @return true if the value can be represented as int value + */ + public static boolean isIntValue(BigDecimal value) { + // TODO impl isIntValue() without exceptions + try { + value.intValueExact(); + return true; + } catch (ArithmeticException ex) { + // ignored + } + return false; + } + + /** + * Returns whether the specified {@link BigDecimal} value can be represented as long. + * + *

If this returns true you can call {@link BigDecimal#longValueExact()} without fear of an {@link ArithmeticException}.

+ * + * @param value the {@link BigDecimal} to check + * @return true if the value can be represented as long value + */ + public static boolean isLongValue(BigDecimal value) { + // TODO impl isLongValue() without exceptions + try { + value.longValueExact(); + return true; + } catch (ArithmeticException ex) { + // ignored + } + return false; + } + + /** + * Returns whether the specified {@link BigDecimal} value can be represented as double. + * + *

If this returns true you can call {@link BigDecimal#doubleValue()} + * without fear of getting {@link Double#POSITIVE_INFINITY} or {@link Double#NEGATIVE_INFINITY} as result.

+ * + *

Example: BigDecimalMath.isDoubleValue(new BigDecimal("1E309")) returns false, + * because new BigDecimal("1E309").doubleValue() returns Infinity.

+ * + *

Note: This method does not check for possible loss of precision.

+ * + *

For example BigDecimalMath.isDoubleValue(new BigDecimal("1.23400000000000000000000000000000001")) will return true, + * because new BigDecimal("1.23400000000000000000000000000000001").doubleValue() returns a valid double value, + * although it loses precision and returns 1.234.

+ * + *

BigDecimalMath.isDoubleValue(new BigDecimal("1E-325")) will return true + * although this value is smaller than {@link Double#MIN_VALUE} (and therefore outside the range of values that can be represented as double) + * because new BigDecimal("1E-325").doubleValue() returns 0 which is a legal value with loss of precision.

+ * + * @param value the {@link BigDecimal} to check + * @return true if the value can be represented as double value + */ + public static boolean isDoubleValue(BigDecimal value) { + if (value.compareTo(DOUBLE_MAX_VALUE) > 0) { + return false; + } + if (value.compareTo(DOUBLE_MAX_VALUE.negate()) < 0) { + return false; + } + + return true; + } + + /** + * Returns the mantissa of the specified {@link BigDecimal} written as mantissa * 10exponent. + * + *

The mantissa is defined as having exactly 1 digit before the decimal point.

+ * + * @param value the {@link BigDecimal} + * @return the mantissa + * @see #exponent(BigDecimal) + */ + public static BigDecimal mantissa(BigDecimal value) { + int exponent = exponent(value); + if (exponent == 0) { + return value; + } + + return value.movePointLeft(exponent); + } + + /** + * Returns the exponent of the specified {@link BigDecimal} written as mantissa * 10exponent. + * + *

The mantissa is defined as having exactly 1 digit before the decimal point.

+ * + * @param value the {@link BigDecimal} + * @return the exponent + * @see #mantissa(BigDecimal) + */ + public static int exponent(BigDecimal value) { + return value.precision() - value.scale() - 1; + } + + /** + * Returns the number of significant digits of the specified {@link BigDecimal}. + * + *

The result contains the number of all digits before the decimal point and + * all digits after the decimal point excluding trailing zeroes.

+ * + *

Examples:

+ *
    + *
  • significantDigits(new BigDecimal("12300.00")) returns 5
  • + *
  • significantDigits(new BigDecimal("1.23000")) returns 3
  • + *
  • significantDigits(new BigDecimal("0.00012300")) returns 3
  • + *
  • significantDigits(new BigDecimal("12300.4500")) returns 7
  • + *
+ * + *

See: Wikipedia: Significant figures

+ * + * @param value the {@link BigDecimal} + * @return the number of significant digits + * @see BigDecimal#stripTrailingZeros() + * @see BigDecimal#precision() + */ + public static int significantDigits(BigDecimal value) { + BigDecimal stripped = value.stripTrailingZeros(); + if (stripped.scale() >= 0) { + return stripped.precision(); + } else { + return stripped.precision() - stripped.scale(); + } + } + + /** + * Returns the integral part of the specified {@link BigDecimal} (left of the decimal point). + * + * @param value the {@link BigDecimal} + * @return the integral part + * @see #fractionalPart(BigDecimal) + */ + public static BigDecimal integralPart(BigDecimal value) { + return value.setScale(0, BigDecimal.ROUND_DOWN); + } + + /** + * Returns the fractional part of the specified {@link BigDecimal} (right of the decimal point). + * + * @param value the {@link BigDecimal} + * @return the fractional part + * @see #integralPart(BigDecimal) + */ + public static BigDecimal fractionalPart(BigDecimal value) { + return value.subtract(integralPart(value)); + } + + /** + * Rounds the specified {@link BigDecimal} to the precision of the specified {@link MathContext}. + * + *

This method calls {@link BigDecimal#round(MathContext)}.

+ * + * @param value the {@link BigDecimal} to round + * @param mathContext the {@link MathContext} used for the result + * @return the rounded {@link BigDecimal} value + * @see BigDecimal#round(MathContext) + * @see BigDecimalMath#roundWithTrailingZeroes(BigDecimal, MathContext) + */ + public static BigDecimal round(BigDecimal value, MathContext mathContext) { + return value.round(mathContext); + } + + /** + * Rounds the specified {@link BigDecimal} to the precision of the specified {@link MathContext} including trailing zeroes. + * + *

This method is similar to {@link BigDecimal#round(MathContext)} but does not remove the trailing zeroes.

+ * + *

Example:

+
+MathContext mc = new MathContext(5);
+System.out.println(BigDecimalMath.roundWithTrailingZeroes(new BigDecimal("1.234567"), mc));    // 1.2346
+System.out.println(BigDecimalMath.roundWithTrailingZeroes(new BigDecimal("123.4567"), mc));    // 123.46
+System.out.println(BigDecimalMath.roundWithTrailingZeroes(new BigDecimal("0.001234567"), mc)); // 0.0012346
+System.out.println(BigDecimalMath.roundWithTrailingZeroes(new BigDecimal("1.23"), mc));        // 1.2300
+System.out.println(BigDecimalMath.roundWithTrailingZeroes(new BigDecimal("1.230000"), mc));    // 1.2300
+System.out.println(BigDecimalMath.roundWithTrailingZeroes(new BigDecimal("0.00123"), mc));     // 0.0012300
+System.out.println(BigDecimalMath.roundWithTrailingZeroes(new BigDecimal("0"), mc));           // 0.0000
+System.out.println(BigDecimalMath.roundWithTrailingZeroes(new BigDecimal("0.00000000"), mc));  // 0.0000
+
+ * + * @param value the {@link BigDecimal} to round + * @param mathContext the {@link MathContext} used for the result + * @return the rounded {@link BigDecimal} value including trailing zeroes + * @see BigDecimal#round(MathContext) + * @see BigDecimalMath#round(BigDecimal, MathContext) + */ + public static BigDecimal roundWithTrailingZeroes(BigDecimal value, MathContext mathContext) { + if (value.precision() == mathContext.getPrecision()) { + return value; + } + if (value.signum() == 0) { + return BigDecimal.ZERO.setScale(mathContext.getPrecision() - 1); + } + + try { + BigDecimal stripped = value.stripTrailingZeros(); + int exponentStripped = exponent(stripped); // value.precision() - value.scale() - 1; + + BigDecimal zero; + if (exponentStripped < -1) { + zero = BigDecimal.ZERO.setScale(mathContext.getPrecision() - exponentStripped); + } else { + zero = BigDecimal.ZERO.setScale(mathContext.getPrecision() + exponentStripped + 1); + } + return stripped.add(zero, mathContext); + } catch (ArithmeticException ex) { + return value.round(mathContext); + } + } + + /** + * Calculates the reciprocal of the specified {@link BigDecimal}. + * + * @param x the {@link BigDecimal} + * @param mathContext the {@link MathContext} used for the result + * @return the reciprocal {@link BigDecimal} + * @throws ArithmeticException if x = 0 + * @throws ArithmeticException if the result is inexact but the + * rounding mode is {@code UNNECESSARY} or + * {@code mc.precision == 0} and the quotient has a + * non-terminating decimal expansion. + */ + public static BigDecimal reciprocal(BigDecimal x, MathContext mathContext) { + return BigDecimal.ONE.divide(x, mathContext); + } + + /** + * Calculates the factorial of the specified integer argument. + * + *

factorial = 1 * 2 * 3 * ... n

+ * + * @param n the {@link BigDecimal} + * @return the factorial {@link BigDecimal} + * @throws ArithmeticException if x < 0 + */ + public static BigDecimal factorial(int n) { + if (n < 0) { + throw new ArithmeticException("Illegal factorial(n) for n < 0: n = " + n); + } + if (n < factorialCache.length) { + return factorialCache[n]; + } + + BigDecimal result = factorialCache[factorialCache.length - 1]; + return result.multiply(factorialRecursion(factorialCache.length, n)); + } + + private static BigDecimal factorialLoop(int n1, final int n2) { + final long limit = Long.MAX_VALUE / n2; + long accu = 1; + BigDecimal result = BigDecimal.ONE; + while (n1 <= n2) { + if (accu <= limit) { + accu *= n1; + } else { + result = result.multiply(BigDecimal.valueOf(accu)); + accu = n1; + } + n1++; + } + return result.multiply(BigDecimal.valueOf(accu)); + } + + private static BigDecimal factorialRecursion(final int n1, final int n2) { + int threshold = n1 > 200 ? 80 : 150; + if (n2 - n1 < threshold) { + return factorialLoop(n1, n2); + } + final int mid = (n1 + n2) >> 1; + return factorialRecursion(mid + 1, n2).multiply(factorialRecursion(n1, mid)); + } + + /** + * Calculates the factorial of the specified {@link BigDecimal}. + * + *

This implementation uses + * Spouge's approximation + * to calculate the factorial for non-integer values.

+ * + *

This involves calculating a series of constants that depend on the desired precision. + * Since this constant calculation is quite expensive (especially for higher precisions), + * the constants for a specific precision will be cached + * and subsequent calls to this method with the same precision will be much faster.

+ * + *

It is therefore recommended to do one call to this method with the standard precision of your application during the startup phase + * and to avoid calling it with many different precisions.

+ * + *

See: Wikipedia: Factorial - Extension of factorial to non-integer values of argument

+ * + * @param x the {@link BigDecimal} + * @param mathContext the {@link MathContext} used for the result + * @return the factorial {@link BigDecimal} + * @throws ArithmeticException if x is a negative integer value (-1, -2, -3, ...) + * @throws UnsupportedOperationException if x is a non-integer value and the {@link MathContext} has unlimited precision + * @see #factorial(int) + * @see #gamma(BigDecimal, MathContext) + */ + public static BigDecimal factorial(BigDecimal x, MathContext mathContext) { + if (isIntValue(x)) { + return round(factorial(x.intValueExact()), mathContext); + } + + // https://en.wikipedia.org/wiki/Spouge%27s_approximation + checkMathContext(mathContext); + MathContext mc = new MathContext(mathContext.getPrecision() << 1, mathContext.getRoundingMode()); + + int a = mathContext.getPrecision() * 13 / 10; + List constants = getSpougeFactorialConstants(a); + + BigDecimal bigA = BigDecimal.valueOf(a); + + boolean negative = false; + BigDecimal factor = constants.get(0); + for (int k = 1; k < a; k++) { + BigDecimal bigK = BigDecimal.valueOf(k); + factor = factor.add(constants.get(k).divide(x.add(bigK), mc)); + negative = !negative; + } + + BigDecimal result = pow(x.add(bigA), x.add(BigDecimal.valueOf(0.5)), mc); + result = result.multiply(exp(x.negate().subtract(bigA), mc)); + result = result.multiply(factor); + + return round(result, mathContext); + } + + static List getSpougeFactorialConstants(int a) { + synchronized (spougeFactorialConstantsCacheLock) { + return spougeFactorialConstantsCache.computeIfAbsent(a, key -> { + List constants = new ArrayList<>(a); + MathContext mc = new MathContext(a * 15 / 10); + + BigDecimal c0 = sqrt(pi(mc).multiply(TWO, mc), mc); + constants.add(c0); + + boolean negative = false; + for (int k = 1; k