WarpPI/core/src/main/java/org/nevec/rjm/Rational.java

901 lines
22 KiB
Java

package org.nevec.rjm;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.math.RoundingMode;
import it.cavallium.warppi.util.Error;
import it.cavallium.warppi.util.Errors;
/**
* Fractions (rational numbers). They are divisions of two BigInteger numbers,
* reduced to coprime numerator and denominator.
*
* @since 2006-06-25
* @author Richard J. Mathar
*/
public class Rational implements Cloneable, Comparable<Rational> {
/**
* numerator
*/
BigInteger a;
/**
* denominator, always larger than zero.
*/
BigInteger b;
/**
* The maximum and minimum value of a standard Java integer, 2^31.
*
* @since 2009-05-18
*/
static public BigInteger MAX_INT = new BigInteger("2147483647");
static public BigInteger MIN_INT = new BigInteger("-2147483648");
/**
* The constant 1.
*/
public static Rational ONE = new Rational(1, 1);
/**
* The constant 0.
*/
static public Rational ZERO = new Rational();
/**
* The constant 1/2
*
* @since 2010-05-25
*/
static public Rational HALF = new Rational(1, 2);
/**
* Default ctor, which represents the zero.
*
* @since 2007-11-17
*/
public Rational() {
a = BigInteger.ZERO;
b = BigInteger.ONE;
}
/**
* ctor from a numerator and denominator.
*
* @param a
* the numerator.
* @param b
* the denominator.
*/
public Rational(final BigInteger a, final BigInteger b) {
this.a = a;
this.b = b;
normalize();
}
/**
* ctor from a numerator.
*
* @param a
* the BigInteger.
*/
public Rational(final BigInteger a) {
this.a = a;
b = new BigInteger("1");
}
/**
* ctor from a numerator and denominator.
*
* @param a
* the numerator.
* @param b
* the denominator.
*/
public Rational(final int a, final int b) {
this(new BigInteger("" + a), new BigInteger("" + b));
}
/**
* ctor from an integer.
*
* @param n
* the integer to be represented by the new instance.
* @since 2010-07-18
*/
public Rational(final int n) {
this(n, 1);
}
/**
* ctor from a string representation.
*
* @param str
* the string. This either has a slash in it, separating two
* integers, or, if there is no slash, is representing the
* numerator with implicit denominator equal to 1. Warning: this
* does not yet test for a denominator equal to zero
*/
public Rational(final String str) {
this(str, 10);
}
/**
* ctor from a string representation in a specified base.
*
* @param str
* the string. This either has a slash in it, separating two
* integers, or, if there is no slash, is just representing the
* numerator.
* @param radix
* the number base for numerator and denominator Warning: this
* does not yet test for a denominator equal to zero
*/
public Rational(final String str, final int radix) {
final int hasslah = str.indexOf("/");
if (hasslah == -1) {
a = new BigInteger(str, radix);
b = new BigInteger("1", radix);
/* no normalization necessary here */
} else {
/*
* create numerator and denominator separately
*/
a = new BigInteger(str.substring(0, hasslah), radix);
b = new BigInteger(str.substring(hasslah + 1), radix);
normalize();
}
}
/**
* Create a copy.
*
* @since 2008-11-07
*/
@Override
public Rational clone() {
/*
* protected access means this does not work return new
* Rational(a.clone(), b.clone()) ;
*/
final BigInteger aclon = new BigInteger("" + a);
final BigInteger bclon = new BigInteger("" + b);
return new Rational(aclon, bclon);
} /* Rational.clone */
/**
* Multiply by another fraction.
*
* @param val
* a second rational number.
* @return the product of this with the val.
*/
public Rational multiply(final Rational val) {
final BigInteger num = a.multiply(val.a);
final BigInteger deno = b.multiply(val.b);
/*
* Normalization to an coprime format will be done inside the ctor() and
* is not duplicated here.
*/
return new Rational(num, deno);
} /* Rational.multiply */
/**
* Multiply by a BigInteger.
*
* @param val
* a second number.
* @return the product of this with the value.
*/
public Rational multiply(final BigInteger val) {
final Rational val2 = new Rational(val, BigInteger.ONE);
return multiply(val2);
} /* Rational.multiply */
/**
* Multiply by an integer.
*
* @param val
* a second number.
* @return the product of this with the value.
*/
public Rational multiply(final int val) {
final BigInteger tmp = new BigInteger("" + val);
return multiply(tmp);
} /* Rational.multiply */
/**
* Power to an integer.
*
* @param exponent
* the exponent.
* @return this value raised to the power given by the exponent. If the
* exponent is 0, the value 1 is returned.
*/
public Rational pow(final int exponent) {
if (exponent == 0) {
return new Rational(1, 1);
}
final BigInteger num = a.pow(Math.abs(exponent));
final BigInteger deno = b.pow(Math.abs(exponent));
if (exponent > 0) {
return new Rational(num, deno);
} else {
return new Rational(deno, num);
}
} /* Rational.pow */
/**
* Power to an integer.
*
* @param exponent
* the exponent.
* @return this value raised to the power given by the exponent. If the
* exponent is 0, the value 1 is returned.
* @throws Error
* @since 2009-05-18
*/
public Rational pow(final BigInteger exponent) throws Error {
/* test for overflow */
if (exponent.compareTo(Rational.MAX_INT) == 1) {
throw new Error(Errors.NUMBER_TOO_LARGE);
}
if (exponent.compareTo(Rational.MIN_INT) == -1) {
throw new Error(Errors.NUMBER_TOO_SMALL);
}
/* promote to the simpler interface above */
return pow(exponent.intValue());
} /* Rational.pow */
/**
* r-th root.
*
* @param r
* the inverse of the exponent. 2 for the square root, 3 for the
* third root etc
* @return this value raised to the inverse power given by the root
* argument, this^(1/r).
* @throws Error
* @since 2009-05-18
*/
public Rational root(final BigInteger r) throws Error {
/* test for overflow */
if (r.compareTo(Rational.MAX_INT) == 1) {
throw new Error(Errors.NUMBER_TOO_LARGE);
}
if (r.compareTo(Rational.MIN_INT) == -1) {
throw new Error(Errors.NUMBER_TOO_SMALL);
}
final int rthroot = r.intValue();
/* cannot pull root of a negative value with even-valued root */
if (compareTo(Rational.ZERO) == -1 && rthroot % 2 == 0) {
throw new Error(Errors.NEGATIVE_PARAMETER);
}
/*
* extract a sign such that we calculate |n|^(1/r), still r carrying any
* sign
*/
final boolean flipsign = compareTo(Rational.ZERO) == -1 && rthroot % 2 != 0 ? true : false;
/*
* delegate the main work to ifactor#root()
*/
final Ifactor num = new Ifactor(a.abs());
final Ifactor deno = new Ifactor(b);
final Rational resul = num.root(rthroot).divide(deno.root(rthroot));
if (flipsign) {
return resul.negate();
} else {
return resul;
}
} /* Rational.root */
/**
* Raise to a rational power.
*
* @param exponent
* The exponent.
* @return This value raised to the power given by the exponent. If the
* exponent is 0, the value 1 is returned.
* @throws Error
* @since 2009-05-18
*/
public Rational pow(final Rational exponent) throws Error {
if (exponent.a.compareTo(BigInteger.ZERO) == 0) {
return new Rational(1, 1);
}
/*
* calculate (a/b)^(exponent.a/exponent.b) as
* ((a/b)^exponent.a)^(1/exponent.b) = tmp^(1/exponent.b)
*/
final Rational tmp = pow(exponent.a);
return tmp.root(exponent.b);
} /* Rational.pow */
/**
* Divide by another fraction.
*
* @param val
* A second rational number.
* @return The value of this/val
* @throws Error
*/
public Rational divide(final Rational val) throws Error {
if (val.compareTo(Rational.ZERO) == 0) {
throw new Error(Errors.DIVISION_BY_ZERO);
}
final BigInteger num = a.multiply(val.b);
final BigInteger deno = b.multiply(val.a);
/*
* Reduction to a coprime format is done inside the ctor, and not
* repeated here.
*/
return new Rational(num, deno);
} /* Rational.divide */
/**
* Divide by an integer.
*
* @param val
* a second number.
* @return the value of this/val
* @throws Error
*/
public Rational divide(final BigInteger val) throws Error {
if (val.compareTo(BigInteger.ZERO) == 0) {
throw new Error(Errors.DIVISION_BY_ZERO);
}
final Rational val2 = new Rational(val, BigInteger.ONE);
return divide(val2);
} /* Rational.divide */
/**
* Divide by an integer.
*
* @param val
* A second number.
* @return The value of this/val
* @throws Error
*/
public Rational divide(final int val) throws Error {
if (val == 0) {
throw new Error(Errors.DIVISION_BY_ZERO);
}
final Rational val2 = new Rational(val, 1);
return divide(val2);
} /* Rational.divide */
/**
* Add another fraction.
*
* @param val
* The number to be added
* @return this+val.
*/
public Rational add(final Rational val) {
final BigInteger num = a.multiply(val.b).add(b.multiply(val.a));
final BigInteger deno = b.multiply(val.b);
return new Rational(num, deno);
} /* Rational.add */
/**
* Add another integer.
*
* @param val
* The number to be added
* @return this+val.
*/
public Rational add(final BigInteger val) {
final Rational val2 = new Rational(val, BigInteger.ONE);
return add(val2);
} /* Rational.add */
/**
* Add another integer.
*
* @param val
* The number to be added
* @return this+val.
* @since May 26 2010
*/
public Rational add(final int val) {
final BigInteger val2 = a.add(b.multiply(new BigInteger("" + val)));
return new Rational(val2, b);
} /* Rational.add */
/**
* Compute the negative.
*
* @return -this.
*/
public Rational negate() {
return new Rational(a.negate(), b);
} /* Rational.negate */
/**
* Subtract another fraction.
*
* @param val
* the number to be subtracted from this
* @return this - val.
*/
public Rational subtract(final Rational val) {
final Rational val2 = val.negate();
return add(val2);
} /* Rational.subtract */
/**
* Subtract an integer.
*
* @param val
* the number to be subtracted from this
* @return this - val.
*/
public Rational subtract(final BigInteger val) {
final Rational val2 = new Rational(val, BigInteger.ONE);
return subtract(val2);
} /* Rational.subtract */
/**
* Subtract an integer.
*
* @param val
* the number to be subtracted from this
* @return this - val.
*/
public Rational subtract(final int val) {
final Rational val2 = new Rational(val, 1);
return subtract(val2);
} /* Rational.subtract */
/**
* binomial (n choose m).
*
* @param n
* the numerator. Equals the size of the set to choose from.
* @param m
* the denominator. Equals the number of elements to select.
* @return the binomial coefficient.
* @since 2006-06-27
* @author Richard J. Mathar
* @throws Error
*/
public static Rational binomial(final Rational n, final BigInteger m) throws Error {
if (m.compareTo(BigInteger.ZERO) == 0) {
return Rational.ONE;
}
Rational bin = n;
for (BigInteger i = new BigInteger("2"); i.compareTo(m) != 1; i = i.add(BigInteger.ONE)) {
bin = bin.multiply(n.subtract(i.subtract(BigInteger.ONE))).divide(i);
}
return bin;
} /* Rational.binomial */
/**
* binomial (n choose m).
*
* @param n
* the numerator. Equals the size of the set to choose from.
* @param m
* the denominator. Equals the number of elements to select.
* @return the binomial coefficient.
* @since 2009-05-19
* @author Richard J. Mathar
* @throws Error
*/
public static Rational binomial(final Rational n, final int m) throws Error {
if (m == 0) {
return Rational.ONE;
}
Rational bin = n;
for (int i = 2; i <= m; i++) {
bin = bin.multiply(n.subtract(i - 1)).divide(i);
}
return bin;
} /* Rational.binomial */
/**
* Hankel's symbol (n,k)
*
* @param n
* the first parameter.
* @param k
* the second parameter, greater or equal to 0.
* @return Gamma(n+k+1/2)/k!/GAMMA(n-k+1/2)
* @since 2010-07-18
* @author Richard J. Mathar
* @throws Error
*/
public static Rational hankelSymb(final Rational n, final int k) throws Error {
if (k == 0) {
return Rational.ONE;
} else if (k < 0) {
throw new Error(Errors.NEGATIVE_PARAMETER);
}
Rational nkhalf = n.subtract(k).add(Rational.HALF);
nkhalf = nkhalf.Pochhammer(2 * k);
final Factorial f = new Factorial();
return nkhalf.divide(f.at(k));
} /* Rational.binomial */
/**
* Get the numerator.
*
* @return The numerator of the reduced fraction.
*/
public BigInteger numer() {
return a;
}
/**
* Get the denominator.
*
* @return The denominator of the reduced fraction.
*/
public BigInteger denom() {
return b;
}
/**
* Absolute value.
*
* @return The absolute (non-negative) value of this.
*/
public Rational abs() {
return new Rational(a.abs(), b.abs());
}
/**
* floor(): the nearest integer not greater than this.
*
* @return The integer rounded towards negative infinity.
*/
public BigInteger floor() {
/*
* is already integer: return the numerator
*/
if (b.compareTo(BigInteger.ONE) == 0) {
return a;
} else if (a.compareTo(BigInteger.ZERO) > 0) {
return a.divide(b);
} else {
return a.divide(b).subtract(BigInteger.ONE);
}
} /* Rational.floor */
/**
* ceil(): the nearest integer not smaller than this.
*
* @return The integer rounded towards positive infinity.
* @since 2010-05-26
*/
public BigInteger ceil() {
/*
* is already integer: return the numerator
*/
if (b.compareTo(BigInteger.ONE) == 0) {
return a;
} else if (a.compareTo(BigInteger.ZERO) > 0) {
return a.divide(b).add(BigInteger.ONE);
} else {
return a.divide(b);
}
} /* Rational.ceil */
/**
* Remove the fractional part.
*
* @return The integer rounded towards zero.
*/
public BigInteger trunc() {
/*
* is already integer: return the numerator
*/
if (b.compareTo(BigInteger.ONE) == 0) {
return a;
} else {
return a.divide(b);
}
} /* Rational.trunc */
/**
* Compares the value of this with another constant.
*
* @param val
* the other constant to compare with
* @return -1, 0 or 1 if this number is numerically less than, equal to, or
* greater than val.
*/
@Override
public int compareTo(final Rational val) {
/*
* Since we have always kept the denominators positive, simple
* cross-multiplying works without changing the sign.
*/
final BigInteger left = a.multiply(val.b);
final BigInteger right = val.a.multiply(b);
return left.compareTo(right);
} /* Rational.compareTo */
/**
* Compares the value of this with another constant.
*
* @param val
* the other constant to compare with
* @return -1, 0 or 1 if this number is numerically less than, equal to, or
* greater than val.
*/
public int compareTo(final BigInteger val) {
final Rational val2 = new Rational(val, BigInteger.ONE);
return compareTo(val2);
} /* Rational.compareTo */
/**
* Return a string in the format number/denom. If the denominator equals 1,
* print just the numerator without a slash.
*
* @return the human-readable version in base 10
*/
@Override
public String toString() {
if (b.compareTo(BigInteger.ONE) != 0) {
return a.toString() + "/" + b.toString();
} else {
return a.toString();
}
} /* Rational.toString */
/**
* Return a double value representation.
*
* @return The value with double precision.
* @since 2008-10-26
*/
public double doubleValue() {
/*
* To meet the risk of individual overflows of the exponents of a
* separate invocation a.doubleValue() or b.doubleValue(), we divide
* first in a BigDecimal environment and convert the result.
*/
final BigDecimal adivb = new BigDecimal(a).divide(new BigDecimal(b), MathContext.DECIMAL128);
return adivb.doubleValue();
} /* Rational.doubleValue */
/**
* Return a float value representation.
*
* @return The value with single precision.
* @since 2009-08-06
*/
public float floatValue() {
final BigDecimal adivb = new BigDecimal(a).divide(new BigDecimal(b), MathContext.DECIMAL128);
return adivb.floatValue();
} /* Rational.floatValue */
/**
* Return a representation as BigDecimal.
*
* @param mc
* the mathematical context which determines precision, rounding
* mode etc
* @return A representation as a BigDecimal floating point number.
* @since 2008-10-26
*/
public BigDecimal BigDecimalValue(final MathContext mc) {
/*
* numerator and denominator individually rephrased
*/
final BigDecimal n = new BigDecimal(a);
final BigDecimal d = new BigDecimal(b);
/*
* the problem with n.divide(d,mc) is that the apparent precision might
* be smaller than what is set by mc if the value has a precise
* truncated representation. 1/4 will appear as 0.25, independent of mc
*/
return BigDecimalMath.scalePrec(n.divide(d, mc), mc);
} /* Rational.BigDecimalValue */
/**
* Return a string in floating point format.
*
* @param digits
* The precision (number of digits)
* @return The human-readable version in base 10.
* @since 2008-10-25
*/
public String toFString(final int digits) {
if (b.compareTo(BigInteger.ONE) != 0) {
final MathContext mc = new MathContext(digits, RoundingMode.DOWN);
final BigDecimal f = new BigDecimal(a).divide(new BigDecimal(b), mc);
return f.toString();
} else {
return a.toString();
}
} /* Rational.toFString */
/**
* Compares the value of this with another constant.
*
* @param val
* The other constant to compare with
* @return The arithmetic maximum of this and val.
* @since 2008-10-19
*/
public Rational max(final Rational val) {
if (compareTo(val) > 0) {
return this;
} else {
return val;
}
} /* Rational.max */
/**
* Compares the value of this with another constant.
*
* @param val
* The other constant to compare with
* @return The arithmetic minimum of this and val.
* @since 2008-10-19
*/
public Rational min(final Rational val) {
if (compareTo(val) < 0) {
return this;
} else {
return val;
}
} /* Rational.min */
/**
* Compute Pochhammer's symbol (this)_n.
*
* @param n
* The number of product terms in the evaluation.
* @return Gamma(this+n)/Gamma(this) = this*(this+1)*...*(this+n-1).
* @since 2008-10-25
*/
public Rational Pochhammer(final BigInteger n) {
if (n.compareTo(BigInteger.ZERO) < 0) {
return null;
} else if (n.compareTo(BigInteger.ZERO) == 0) {
return Rational.ONE;
} else {
/*
* initialize results with the current value
*/
Rational res = new Rational(a, b);
BigInteger i = BigInteger.ONE;
for (; i.compareTo(n) < 0; i = i.add(BigInteger.ONE)) {
res = res.multiply(add(i));
}
return res;
}
} /* Rational.pochhammer */
/**
* Compute pochhammer's symbol (this)_n.
*
* @param n
* The number of product terms in the evaluation.
* @return Gamma(this+n)/GAMMA(this).
* @since 2008-11-13
*/
public Rational Pochhammer(final int n) {
return Pochhammer(new BigInteger("" + n));
} /* Rational.pochhammer */
/**
* True if the value is integer. Equivalent to the indication whether a
* conversion to an integer can be exact.
*
* @since 2010-05-26
*/
public boolean isBigInteger() {
return b.abs().compareTo(BigInteger.ONE) == 0;
} /* Rational.isBigInteger */
/**
* True if the value is integer and in the range of the standard integer.
* Equivalent to the indication whether a conversion to an integer can be
* exact.
*
* @since 2010-05-26
*/
public boolean isInteger() {
if (!isBigInteger()) {
return false;
}
return a.compareTo(Rational.MAX_INT) <= 0 && a.compareTo(Rational.MIN_INT) >= 0;
} /* Rational.isInteger */
/**
* Conversion to an integer value, if this can be done exactly.
*
* @throws Error
*
* @since 2011-02-13
*/
int intValue() throws Error {
if (!isInteger()) {
throw new Error(Errors.CONVERSION_ERROR);
}
return a.intValue();
}
/**
* Conversion to a BigInteger value, if this can be done exactly.
*
* @throws Error
*
* @since 2012-03-02
*/
BigInteger BigIntegerValue() throws Error {
if (!isBigInteger()) {
throw new Error(Errors.CONVERSION_ERROR);
}
return a;
}
/**
* True if the value is a fraction of two integers in the range of the
* standard integer.
*
* @since 2010-05-26
*/
public boolean isIntegerFrac() {
return a.compareTo(Rational.MAX_INT) <= 0 && a.compareTo(Rational.MIN_INT) >= 0 && b.compareTo(Rational.MAX_INT) <= 0 && b.compareTo(Rational.MIN_INT) >= 0;
} /* Rational.isIntegerFrac */
/**
* The sign: 1 if the number is >0, 0 if ==0, -1 if <0
*
* @return the signum of the value.
* @since 2010-05-26
*/
public int signum() {
return b.signum() * a.signum();
} /* Rational.signum */
/**
* Common lcm of the denominators of a set of rational values.
*
* @param vals
* The list/set of the rational values.
* @return LCM(denom of first, denom of second, ..,denom of last)
* @since 2012-03-02
*/
static public BigInteger lcmDenom(final Rational[] vals) {
BigInteger l = BigInteger.ONE;
for (final Rational val : vals) {
l = BigIntegerMath.lcm(l, val.b);
}
return l;
} /* Rational.lcmDenom */
/**
* Normalize to coprime numerator and denominator. Also copy a negative sign
* of the denominator to the numerator.
*
* @since 2008-10-19
*/
protected void normalize() {
/*
* compute greatest common divisor of numerator and denominator
*/
final BigInteger g = a.gcd(b);
if (g.compareTo(BigInteger.ONE) > 0) {
a = a.divide(g);
b = b.divide(g);
}
if (b.compareTo(BigInteger.ZERO) == -1) {
a = a.negate();
b = b.negate();
}
} /* Rational.normalize */
} /* Rational */