DfpField.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* This is not the original file distributed by the Apache Software Foundation
* It has been modified by the Hipparchus project
*/
package org.hipparchus.dfp;
import org.hipparchus.Field;
/** Field for Decimal floating point instances.
*/
public class DfpField implements Field<Dfp> {
/** Enumerate for rounding modes. */
public enum RoundingMode {
/** Rounds toward zero (truncation). */
ROUND_DOWN,
/** Rounds away from zero if discarded digit is non-zero. */
ROUND_UP,
/** Rounds towards nearest unless both are equidistant in which case it rounds away from zero. */
ROUND_HALF_UP,
/** Rounds towards nearest unless both are equidistant in which case it rounds toward zero. */
ROUND_HALF_DOWN,
/** Rounds towards nearest unless both are equidistant in which case it rounds toward the even neighbor.
* This is the default as specified by IEEE 854-1987
*/
ROUND_HALF_EVEN,
/** Rounds towards nearest unless both are equidistant in which case it rounds toward the odd neighbor. */
ROUND_HALF_ODD,
/** Rounds towards positive infinity. */
ROUND_CEIL,
/** Rounds towards negative infinity. */
ROUND_FLOOR;
}
/** IEEE 854-1987 flag for invalid operation. */
public static final int FLAG_INVALID = 1;
/** IEEE 854-1987 flag for division by zero. */
public static final int FLAG_DIV_ZERO = 2;
/** IEEE 854-1987 flag for overflow. */
public static final int FLAG_OVERFLOW = 4;
/** IEEE 854-1987 flag for underflow. */
public static final int FLAG_UNDERFLOW = 8;
/** IEEE 854-1987 flag for inexact result. */
public static final int FLAG_INEXACT = 16;
/** High precision string representation of √2. */
private static String sqr2String;
// Note: the static strings are set up (once) by the ctor and @GuardedBy("DfpField.class")
/** High precision string representation of √2 / 2. */
private static String sqr2ReciprocalString;
/** High precision string representation of √3. */
private static String sqr3String;
/** High precision string representation of √3 / 3. */
private static String sqr3ReciprocalString;
/** High precision string representation of π. */
private static String piString;
/** High precision string representation of e. */
private static String eString;
/** High precision string representation of ln(2). */
private static String ln2String;
/** High precision string representation of ln(5). */
private static String ln5String;
/** High precision string representation of ln(10). */
private static String ln10String;
/** The number of radix digits.
* Note these depend on the radix which is 10000 digits,
* so each one is equivalent to 4 decimal digits.
*/
private final int radixDigits;
/** A {@link Dfp} with value 0. */
private final Dfp zero;
/** A {@link Dfp} with value 1. */
private final Dfp one;
/** A {@link Dfp} with value 2. */
private final Dfp two;
/** A {@link Dfp} with value √2. */
private final Dfp sqr2;
/** A two elements {@link Dfp} array with value √2 split in two pieces. */
private final Dfp[] sqr2Split;
/** A {@link Dfp} with value √2 / 2. */
private final Dfp sqr2Reciprocal;
/** A {@link Dfp} with value √3. */
private final Dfp sqr3;
/** A {@link Dfp} with value √3 / 3. */
private final Dfp sqr3Reciprocal;
/** A {@link Dfp} with value π. */
private final Dfp pi;
/** A {@link Dfp} for converting degrees to radians. */
private final Dfp degToRad;
/** A {@link Dfp} for converting radians to degrees. */
private final Dfp radToDeg;
/** A two elements {@link Dfp} array with value π split in two pieces. */
private final Dfp[] piSplit;
/** A {@link Dfp} with value e. */
private final Dfp e;
/** A two elements {@link Dfp} array with value e split in two pieces. */
private final Dfp[] eSplit;
/** A {@link Dfp} with value ln(2). */
private final Dfp ln2;
/** A two elements {@link Dfp} array with value ln(2) split in two pieces. */
private final Dfp[] ln2Split;
/** A {@link Dfp} with value ln(5). */
private final Dfp ln5;
/** A two elements {@link Dfp} array with value ln(5) split in two pieces. */
private final Dfp[] ln5Split;
/** A {@link Dfp} with value ln(10). */
private final Dfp ln10;
/** Current rounding mode. */
private RoundingMode rMode;
/** IEEE 854-1987 signals. */
private int ieeeFlags;
/** Create a factory for the specified number of radix digits.
* <p>
* Note that since the {@link Dfp} class uses 10000 as its radix, each radix
* digit is equivalent to 4 decimal digits. This implies that asking for
* 13, 14, 15 or 16 decimal digits will really lead to a 4 radix 10000 digits in
* all cases.
* </p>
* @param decimalDigits minimal number of decimal digits.
*/
public DfpField(final int decimalDigits) {
this(decimalDigits, true);
}
/** Create a factory for the specified number of radix digits.
* <p>
* Note that since the {@link Dfp} class uses 10000 as its radix, each radix
* digit is equivalent to 4 decimal digits. This implies that asking for
* 13, 14, 15 or 16 decimal digits will really lead to a 4 radix 10000 digits in
* all cases.
* </p>
* @param decimalDigits minimal number of decimal digits
* @param computeConstants if true, the transcendental constants for the given precision
* must be computed (setting this flag to false is RESERVED for the internal recursive call)
*/
private DfpField(final int decimalDigits, final boolean computeConstants) {
this.radixDigits = (decimalDigits < 13) ? 4 : (decimalDigits + 3) / 4;
this.rMode = RoundingMode.ROUND_HALF_EVEN;
this.ieeeFlags = 0;
this.zero = new Dfp(this, 0);
this.one = new Dfp(this, 1);
this.two = new Dfp(this, 2);
if (computeConstants) {
// set up transcendental constants
synchronized (DfpField.class) {
// as a heuristic to circumvent Table-Maker's Dilemma, we set the string
// representation of the constants to be at least 3 times larger than the
// number of decimal digits, also as an attempt to really compute these
// constants only once, we set a minimum number of digits
computeStringConstants((decimalDigits < 67) ? 200 : (3 * decimalDigits));
// set up the constants at current field accuracy
sqr2 = new Dfp(this, sqr2String);
sqr2Split = split(sqr2String);
sqr2Reciprocal = new Dfp(this, sqr2ReciprocalString);
sqr3 = new Dfp(this, sqr3String);
sqr3Reciprocal = new Dfp(this, sqr3ReciprocalString);
pi = new Dfp(this, piString);
degToRad = pi.divide(180.0);
radToDeg = pi.divide(180.0).reciprocal();
piSplit = split(piString);
e = new Dfp(this, eString);
eSplit = split(eString);
ln2 = new Dfp(this, ln2String);
ln2Split = split(ln2String);
ln5 = new Dfp(this, ln5String);
ln5Split = split(ln5String);
ln10 = new Dfp(this, ln10String);
}
} else {
// dummy settings for unused constants
sqr2 = null;
sqr2Split = null;
sqr2Reciprocal = null;
sqr3 = null;
sqr3Reciprocal = null;
pi = null;
degToRad = null;
radToDeg = null;
piSplit = null;
e = null;
eSplit = null;
ln2 = null;
ln2Split = null;
ln5 = null;
ln5Split = null;
ln10 = null;
}
}
/** Get the number of radix digits of the {@link Dfp} instances built by this factory.
* @return number of radix digits
*/
public int getRadixDigits() {
return radixDigits;
}
/** Set the rounding mode.
* If not set, the default value is {@link RoundingMode#ROUND_HALF_EVEN}.
* @param mode desired rounding mode
* Note that the rounding mode is common to all {@link Dfp} instances
* belonging to the current {@link DfpField} in the system and will
* affect all future calculations.
*/
public void setRoundingMode(final RoundingMode mode) {
rMode = mode;
}
/** Get the current rounding mode.
* @return current rounding mode
*/
public RoundingMode getRoundingMode() {
return rMode;
}
/** Get the IEEE 854 status flags.
* @return IEEE 854 status flags
* @see #clearIEEEFlags()
* @see #setIEEEFlags(int)
* @see #setIEEEFlagsBits(int)
* @see #FLAG_INVALID
* @see #FLAG_DIV_ZERO
* @see #FLAG_OVERFLOW
* @see #FLAG_UNDERFLOW
* @see #FLAG_INEXACT
*/
public int getIEEEFlags() {
return ieeeFlags;
}
/** Clears the IEEE 854 status flags.
* @see #getIEEEFlags()
* @see #setIEEEFlags(int)
* @see #setIEEEFlagsBits(int)
* @see #FLAG_INVALID
* @see #FLAG_DIV_ZERO
* @see #FLAG_OVERFLOW
* @see #FLAG_UNDERFLOW
* @see #FLAG_INEXACT
*/
public void clearIEEEFlags() {
ieeeFlags = 0;
}
/** Sets the IEEE 854 status flags.
* @param flags desired value for the flags
* @see #getIEEEFlags()
* @see #clearIEEEFlags()
* @see #setIEEEFlagsBits(int)
* @see #FLAG_INVALID
* @see #FLAG_DIV_ZERO
* @see #FLAG_OVERFLOW
* @see #FLAG_UNDERFLOW
* @see #FLAG_INEXACT
*/
public void setIEEEFlags(final int flags) {
ieeeFlags = flags & (FLAG_INVALID | FLAG_DIV_ZERO | FLAG_OVERFLOW | FLAG_UNDERFLOW | FLAG_INEXACT);
}
/** Sets some bits in the IEEE 854 status flags, without changing the already set bits.
* <p>
* Calling this method is equivalent to call {@code setIEEEFlags(getIEEEFlags() | bits)}
* </p>
* @param bits bits to set
* @see #getIEEEFlags()
* @see #clearIEEEFlags()
* @see #setIEEEFlags(int)
* @see #FLAG_INVALID
* @see #FLAG_DIV_ZERO
* @see #FLAG_OVERFLOW
* @see #FLAG_UNDERFLOW
* @see #FLAG_INEXACT
*/
public void setIEEEFlagsBits(final int bits) {
ieeeFlags |= bits & (FLAG_INVALID | FLAG_DIV_ZERO | FLAG_OVERFLOW | FLAG_UNDERFLOW | FLAG_INEXACT);
}
/** Makes a {@link Dfp} with a value of 0.
* @return a new {@link Dfp} with a value of 0
*/
public Dfp newDfp() {
return new Dfp(this);
}
/** Create an instance from a byte value.
* @param x value to convert to an instance
* @return a new {@link Dfp} with the same value as x
*/
public Dfp newDfp(final byte x) {
return new Dfp(this, x);
}
/** Create an instance from an int value.
* @param x value to convert to an instance
* @return a new {@link Dfp} with the same value as x
*/
public Dfp newDfp(final int x) {
return new Dfp(this, x);
}
/** Create an instance from a long value.
* @param x value to convert to an instance
* @return a new {@link Dfp} with the same value as x
*/
public Dfp newDfp(final long x) {
return new Dfp(this, x);
}
/** Create an instance from a double value.
* @param x value to convert to an instance
* @return a new {@link Dfp} with the same value as x
*/
public Dfp newDfp(final double x) {
return new Dfp(this, x);
}
/** Copy constructor.
* @param d instance to copy
* @return a new {@link Dfp} with the same value as d
*/
public Dfp newDfp(Dfp d) {
return new Dfp(d);
}
/** Create a {@link Dfp} given a String representation.
* @param s string representation of the instance
* @return a new {@link Dfp} parsed from specified string
*/
public Dfp newDfp(final String s) {
return new Dfp(this, s);
}
/** Creates a {@link Dfp} with a non-finite value.
* @param sign sign of the Dfp to create
* @param nans code of the value, must be one of {@link Dfp#INFINITE},
* {@link Dfp#SNAN}, {@link Dfp#QNAN}
* @return a new {@link Dfp} with a non-finite value
*/
public Dfp newDfp(final byte sign, final byte nans) {
return new Dfp(this, sign, nans);
}
/** Get the constant 0.
* @return a {@link Dfp} with value 0
*/
@Override
public Dfp getZero() {
return zero;
}
/** Get the constant 1.
* @return a {@link Dfp} with value 1
*/
@Override
public Dfp getOne() {
return one;
}
/** {@inheritDoc} */
@Override
public Class<Dfp> getRuntimeClass() {
return Dfp.class;
}
/** Get the constant 2.
* @return a {@link Dfp} with value 2
*/
public Dfp getTwo() {
return two;
}
/** Get the constant √2.
* @return a {@link Dfp} with value √2
*/
public Dfp getSqr2() {
return sqr2;
}
/** Get the constant √2 split in two pieces.
* @return a {@link Dfp} with value √2 split in two pieces
*/
public Dfp[] getSqr2Split() {
return sqr2Split.clone();
}
/** Get the constant √2 / 2.
* @return a {@link Dfp} with value √2 / 2
*/
public Dfp getSqr2Reciprocal() {
return sqr2Reciprocal;
}
/** Get the constant √3.
* @return a {@link Dfp} with value √3
*/
public Dfp getSqr3() {
return sqr3;
}
/** Get the constant √3 / 3.
* @return a {@link Dfp} with value √3 / 3
*/
public Dfp getSqr3Reciprocal() {
return sqr3Reciprocal;
}
/** Get the constant π.
* @return a {@link Dfp} with value π
*/
public Dfp getPi() {
return pi;
}
/** Get the degrees to radians conversion factor.
* @return a {@link Dfp} for degrees to radians conversion factor
*/
public Dfp getDegToRad() {
return degToRad;
}
/** Get the radians to degrees conversion factor.
* @return a {@link Dfp} for radians to degrees conversion factor
*/
public Dfp getRadToDeg() {
return radToDeg;
}
/** Get the constant π split in two pieces.
* @return a {@link Dfp} with value π split in two pieces
*/
public Dfp[] getPiSplit() {
return piSplit.clone();
}
/** Get the constant e.
* @return a {@link Dfp} with value e
*/
public Dfp getE() {
return e;
}
/** Get the constant e split in two pieces.
* @return a {@link Dfp} with value e split in two pieces
*/
public Dfp[] getESplit() {
return eSplit.clone();
}
/** Get the constant ln(2).
* @return a {@link Dfp} with value ln(2)
*/
public Dfp getLn2() {
return ln2;
}
/** Get the constant ln(2) split in two pieces.
* @return a {@link Dfp} with value ln(2) split in two pieces
*/
public Dfp[] getLn2Split() {
return ln2Split.clone();
}
/** Get the constant ln(5).
* @return a {@link Dfp} with value ln(5)
*/
public Dfp getLn5() {
return ln5;
}
/** Get the constant ln(5) split in two pieces.
* @return a {@link Dfp} with value ln(5) split in two pieces
*/
public Dfp[] getLn5Split() {
return ln5Split.clone();
}
/** Get the constant ln(10).
* @return a {@link Dfp} with value ln(10)
*/
public Dfp getLn10() {
return ln10;
}
/** {@inheritDoc}
* <p>
* Two fields are considered equals if they have the same number
* of radix digits and the same rounding mode.
* </p>
*/
@Override
public boolean equals(final Object other) {
if (this == other) {
return true;
} else if (other instanceof DfpField) {
DfpField rhs = (DfpField) other;
return getRadixDigits() == rhs.getRadixDigits() &&
getRoundingMode() == rhs.getRoundingMode();
} else {
return false;
}
}
/** {@inheritDoc} */
@Override
public int hashCode() {
return 0xdf49a2ca ^ ((radixDigits << 16) & (rMode.ordinal() << 5) & ieeeFlags);
}
/** Breaks a string representation up into two {@link Dfp}'s.
* The split is such that the sum of them is equivalent to the input string,
* but has higher precision than using a single Dfp.
* @param a string representation of the number to split
* @return an array of two {@link Dfp Dfp} instances which sum equals a
*/
private Dfp[] split(final String a) {
Dfp[] result = new Dfp[2];
boolean leading = true;
int sp = 0;
int sig = 0;
StringBuilder builder1 = new StringBuilder(a.length());
for (int i = 0; i < a.length(); i++) {
final char c = a.charAt(i);
builder1.append(c);
if (c >= '1' && c <= '9') {
leading = false;
}
if (c == '.') {
sig += (400 - sig) % 4;
leading = false;
}
if (sig == (radixDigits / 2) * 4) {
sp = i;
break;
}
if (c >= '0' &&c <= '9' && !leading) {
sig ++;
}
}
result[0] = new Dfp(this, builder1.substring(0, sp));
StringBuilder builder2 = new StringBuilder(a.length());
for (int i = 0; i < a.length(); i++) {
final char c = a.charAt(i);
if (c >= '0' && c <= '9' && i < sp) {
builder2.append('0');
} else {
builder2.append(c);
}
}
result[1] = new Dfp(this, builder2.toString());
return result;
}
/** Recompute the high precision string constants.
* @param highPrecisionDecimalDigits precision at which the string constants mus be computed
*/
private static void computeStringConstants(final int highPrecisionDecimalDigits) {
synchronized (DfpField.class) {
if (sqr2String == null || sqr2String.length() < highPrecisionDecimalDigits - 3) {
// recompute the string representation of the transcendental constants
final DfpField highPrecisionField = new DfpField(highPrecisionDecimalDigits, false);
final Dfp highPrecisionOne = new Dfp(highPrecisionField, 1);
final Dfp highPrecisionTwo = new Dfp(highPrecisionField, 2);
final Dfp highPrecisionThree = new Dfp(highPrecisionField, 3);
final Dfp highPrecisionSqr2 = highPrecisionTwo.sqrt();
sqr2String = highPrecisionSqr2.toString();
sqr2ReciprocalString = highPrecisionOne.divide(highPrecisionSqr2).toString();
final Dfp highPrecisionSqr3 = highPrecisionThree.sqrt();
sqr3String = highPrecisionSqr3.toString();
sqr3ReciprocalString = highPrecisionOne.divide(highPrecisionSqr3).toString();
piString = computePi(highPrecisionOne, highPrecisionTwo, highPrecisionThree).toString();
eString = computeExp(highPrecisionOne, highPrecisionOne).toString();
ln2String = computeLn(highPrecisionTwo, highPrecisionOne, highPrecisionTwo).toString();
ln5String = computeLn(new Dfp(highPrecisionField, 5), highPrecisionOne, highPrecisionTwo).toString();
ln10String = computeLn(new Dfp(highPrecisionField, 10), highPrecisionOne, highPrecisionTwo).toString();
}
}
}
/** Compute π using Jonathan and Peter Borwein quartic formula.
* @param one constant with value 1 at desired precision
* @param two constant with value 2 at desired precision
* @param three constant with value 3 at desired precision
* @return π
*/
private static Dfp computePi(final Dfp one, final Dfp two, final Dfp three) {
Dfp sqrt2 = two.sqrt();
Dfp yk = sqrt2.subtract(one);
Dfp four = two.add(two);
Dfp two2kp3 = two;
Dfp ak = two.multiply(three.subtract(two.multiply(sqrt2)));
// The formula converges quartically. This means the number of correct
// digits is multiplied by 4 at each iteration! Five iterations are
// sufficient for about 160 digits, eight iterations give about
// 10000 digits (this has been checked) and 20 iterations more than
// 160 billions of digits (this has NOT been checked).
// So the limit here is considered sufficient for most purposes ...
for (int i = 1; i < 20; i++) {
final Dfp ykM1 = yk;
final Dfp y2 = yk.multiply(yk);
final Dfp oneMinusY4 = one.subtract(y2.multiply(y2));
final Dfp s = oneMinusY4.sqrt().sqrt();
yk = one.subtract(s).divide(one.add(s));
two2kp3 = two2kp3.multiply(four);
final Dfp p = one.add(yk);
final Dfp p2 = p.multiply(p);
ak = ak.multiply(p2.multiply(p2)).subtract(two2kp3.multiply(yk).multiply(one.add(yk).add(yk.multiply(yk))));
if (yk.equals(ykM1)) {
break;
}
}
return one.divide(ak);
}
/** Compute exp(a).
* @param a number for which we want the exponential
* @param one constant with value 1 at desired precision
* @return exp(a)
*/
public static Dfp computeExp(final Dfp a, final Dfp one) {
Dfp y = new Dfp(one);
Dfp py = new Dfp(one);
Dfp f = new Dfp(one);
Dfp fi = new Dfp(one);
Dfp x = new Dfp(one);
for (int i = 0; i < 10000; i++) {
x = x.multiply(a);
y = y.add(x.divide(f));
fi = fi.add(one);
f = f.multiply(fi);
if (y.equals(py)) {
break;
}
py = new Dfp(y);
}
return y;
}
/** Compute ln(a).
*
* Let f(x) = ln(x),
*
* We know that f'(x) = 1/x, thus from Taylor's theorem we have:
*
* ----- n+1 n
* f(x) = \ (-1) (x - 1)
* / ---------------- for 1 <= n <= infinity
* ----- n
*
* or
* 2 3 4
* (x-1) (x-1) (x-1)
* ln(x) = (x-1) - ----- + ------ - ------ + ...
* 2 3 4
*
* alternatively,
*
* 2 3 4
* x x x
* ln(x+1) = x - - + - - - + ...
* 2 3 4
*
* This series can be used to compute ln(x), but it converges too slowly.
*
* If we substitute -x for x above, we get
*
* 2 3 4
* x x x
* ln(1-x) = -x - - - - - - + ...
* 2 3 4
*
* Note that all terms are now negative. Because the even powered ones
* absorbed the sign. Now, subtract the series above from the previous
* one to get ln(x+1) - ln(1-x). Note the even terms cancel out leaving
* only the odd ones
*
* 3 5 7
* 2x 2x 2x
* ln(x+1) - ln(x-1) = 2x + --- + --- + ---- + ...
* 3 5 7
*
* By the property of logarithms that ln(a) - ln(b) = ln (a/b) we have:
*
* 3 5 7
* x+1 / x x x \
* ln ----- = 2 * | x + ---- + ---- + ---- + ... |
* x-1 \ 3 5 7 /
*
* But now we want to find ln(a), so we need to find the value of x
* such that a = (x+1)/(x-1). This is easily solved to find that
* x = (a-1)/(a+1).
* @param a number for which we want the exponential
* @param one constant with value 1 at desired precision
* @param two constant with value 2 at desired precision
* @return ln(a)
*/
public static Dfp computeLn(final Dfp a, final Dfp one, final Dfp two) {
int den = 1;
Dfp x = a.add(new Dfp(a.getField(), -1)).divide(a.add(one));
Dfp y = new Dfp(x);
Dfp num = new Dfp(x);
Dfp py = new Dfp(y);
for (int i = 0; i < 10000; i++) {
num = num.multiply(x);
num = num.multiply(x);
den += 2;
Dfp t = num.divide(den);
y = y.add(t);
if (y.equals(py)) {
break;
}
py = new Dfp(y);
}
return y.multiply(two);
}
/** Get extended field for accuracy conversion.
* @param digitsFactor multiplication factor for number of digits
* @param computeConstants if true, the transcendental constants for the given precision
* must be computed (setting this flag to false is RESERVED for the internal recursive call)
* @return field with extended precision
* @since 1.7
*/
public DfpField getExtendedField(final int digitsFactor, final boolean computeConstants) {
final int oldDecimalDigits = getRadixDigits() * 4;
return new DfpField(oldDecimalDigits * digitsFactor, computeConstants);
}
}