package ch.obermuhlner.math.big; import java.math.BigDecimal; import java.math.BigInteger; import java.math.MathContext; import java.math.RoundingMode; import java.util.ArrayList; import java.util.List; import java.util.stream.IntStream; /** * A rational number represented as a quotient of two values. * *
Basic calculations with rational numbers (+ - * /) have no loss of precision. * This allows to use {@link BigRational} as a replacement for {@link BigDecimal} if absolute accuracy is desired.
* * * *The values are internally stored as {@link BigDecimal} (for performance optimizations) but represented * as {@link BigInteger} (for mathematical correctness) * when accessed with {@link #getNumeratorBigInteger()} and {@link #getDenominatorBigInteger()}.
* *The following basic calculations have no loss of precision:
*The following calculations are special cases of the ones listed above and have no loss of precision:
*Any {@link BigRational} value can be converted into an arbitrary {@link #withPrecision(int) precision} (number of significant digits) * or {@link #withScale(int) scale} (number of digits after the decimal point).
*/ public class BigRational implements ComparableGuaranteed to not be 0.
*Guaranteed to be positive.
* * @return the denominator as BigInteger */ public BigInteger getDenominatorBigInteger() { return denominator.toBigInteger(); } /** * Returns the denominator of this rational number as BigDecimal. * *Guaranteed to not be 0.
*Guaranteed to be positive.
* * @return the denominator as BigDecimal */ public BigDecimal getDenominator() { return denominator; } /** * Reduces this rational number to the smallest numerator/denominator with the same value. * * @return the reduced rational number */ public BigRational reduce() { BigInteger n = numerator.toBigInteger(); BigInteger d = denominator.toBigInteger(); BigInteger gcd = n.gcd(d); n = n.divide(gcd); d = d.divide(gcd); return valueOf(n, d); } /** * Returns the integer part of this rational number. * *Examples:
*BigRational.valueOf(3.5).integerPart()
returns BigRational.valueOf(3)
Examples:
*BigRational.valueOf(3.5).integerPart()
returns BigRational.valueOf(0.5)
The result has no loss of precision.
* *Examples:
*BigRational.valueOf(3.5).negate()
returns BigRational.valueOf(-3.5)
The result has no loss of precision.
* *Examples:
*BigRational.valueOf(0.5).reciprocal()
returns BigRational.valueOf(2)
BigRational.valueOf(-2).reciprocal()
returns BigRational.valueOf(-0.5)
The result has no loss of precision.
* *Examples:
*BigRational.valueOf(-2).abs()
returns BigRational.valueOf(2)
BigRational.valueOf(2).abs()
returns BigRational.valueOf(2)
This is functionally identical to
* this.add(BigRational.ONE)
* but slightly faster.
The result has no loss of precision.
* * @return the incremented rational number */ public BigRational increment() { return of(numerator.add(denominator), denominator); } /** * Calculates the decrement of this rational number (- 1). * *This is functionally identical to
* this.subtract(BigRational.ONE)
* but slightly faster.
The result has no loss of precision.
* * @return the decremented rational number */ public BigRational decrement() { return of(numerator.subtract(denominator), denominator); } /** * Calculates the addition (+) of this rational number and the specified argument. * *The result has no loss of precision.
* * @param value the rational number to add * @return the resulting rational number */ public BigRational add(BigRational value) { if (denominator.equals(value.denominator)) { return of(numerator.add(value.numerator), denominator); } BigDecimal n = numerator.multiply(value.denominator).add(value.numerator.multiply(denominator)); BigDecimal d = denominator.multiply(value.denominator); return of(n, d); } private BigRational add(BigDecimal value) { return of(numerator.add(value.multiply(denominator)), denominator); } /** * Calculates the addition (+) of this rational number and the specified argument. * *This is functionally identical to
* this.add(BigRational.valueOf(value))
* but slightly faster.
The result has no loss of precision.
* * @param value the {@link BigInteger} to add * @return the resulting rational number */ public BigRational add(BigInteger value) { if (value.equals(BigInteger.ZERO)) { return this; } return add(new BigDecimal(value)); } /** * Calculates the addition (+) of this rational number and the specified argument. * *This is functionally identical to
* this.add(BigRational.valueOf(value))
* but slightly faster.
The result has no loss of precision.
* * @param value the int value to add * @return the resulting rational number */ public BigRational add(int value) { if (value == 0) { return this; } return add(BigInteger.valueOf(value)); } /** * Calculates the subtraction (-) of this rational number and the specified argument. * *The result has no loss of precision.
* * @param value the rational number to subtract * @return the resulting rational number */ public BigRational subtract(BigRational value) { if (denominator.equals(value.denominator)) { return of(numerator.subtract(value.numerator), denominator); } BigDecimal n = numerator.multiply(value.denominator).subtract(value.numerator.multiply(denominator)); BigDecimal d = denominator.multiply(value.denominator); return of(n, d); } private BigRational subtract(BigDecimal value) { return of(numerator.subtract(value.multiply(denominator)), denominator); } /** * Calculates the subtraction (-) of this rational number and the specified argument. * *This is functionally identical to
* this.subtract(BigRational.valueOf(value))
* but slightly faster.
The result has no loss of precision.
* * @param value the {@link BigInteger} to subtract * @return the resulting rational number */ public BigRational subtract(BigInteger value) { if (value.equals(BigInteger.ZERO)) { return this; } return subtract(new BigDecimal(value)); } /** * Calculates the subtraction (-) of this rational number and the specified argument. * *This is functionally identical to
* this.subtract(BigRational.valueOf(value))
* but slightly faster.
The result has no loss of precision.
* * @param value the int value to subtract * @return the resulting rational number */ public BigRational subtract(int value) { if (value == 0) { return this; } return subtract(BigInteger.valueOf(value)); } /** * Calculates the multiplication (*) of this rational number and the specified argument. * *The result has no loss of precision.
* * @param value the rational number to multiply * @return the resulting rational number */ public BigRational multiply(BigRational value) { if (isZero() || value.isZero()) { return ZERO; } if (equals(ONE)) { return value; } if (value.equals(ONE)) { return this; } BigDecimal n = numerator.multiply(value.numerator); BigDecimal d = denominator.multiply(value.denominator); return of(n, d); } // private, because we want to hide that we use BigDecimal internally private BigRational multiply(BigDecimal value) { BigDecimal n = numerator.multiply(value); BigDecimal d = denominator; return of(n, d); } /** * Calculates the multiplication (*) of this rational number and the specified argument. * *This is functionally identical to
* this.multiply(BigRational.valueOf(value))
* but slightly faster.
The result has no loss of precision.
* * @param value the {@link BigInteger} to multiply * @return the resulting rational number */ public BigRational multiply(BigInteger value) { if (isZero() || value.signum() == 0) { return ZERO; } if (equals(ONE)) { return valueOf(value); } if (value.equals(BigInteger.ONE)) { return this; } return multiply(new BigDecimal(value)); } /** * Calculates the multiplication (*) of this rational number and the specified argument. * *This is functionally identical to
* this.multiply(BigRational.valueOf(value))
* but slightly faster.
The result has no loss of precision.
* * @param value the int value to multiply * @return the resulting rational number */ public BigRational multiply(int value) { return multiply(BigInteger.valueOf(value)); } /** * Calculates the division (/) of this rational number and the specified argument. * *The result has no loss of precision.
* * @param value the rational number to divide (0 is not allowed) * @return the resulting rational number * @throws ArithmeticException if the argument is 0 (division by zero) */ public BigRational divide(BigRational value) { if (value.equals(ONE)) { return this; } BigDecimal n = numerator.multiply(value.denominator); BigDecimal d = denominator.multiply(value.numerator); return of(n, d); } private BigRational divide(BigDecimal value) { BigDecimal n = numerator; BigDecimal d = denominator.multiply(value); return of(n, d); } /** * Calculates the division (/) of this rational number and the specified argument. * *This is functionally identical to
* this.divide(BigRational.valueOf(value))
* but slightly faster.
The result has no loss of precision.
* * @param value the {@link BigInteger} to divide (0 is not allowed) * @return the resulting rational number * @throws ArithmeticException if the argument is 0 (division by zero) */ public BigRational divide(BigInteger value) { if (value.equals(BigInteger.ONE)) { return this; } return divide(new BigDecimal(value)); } /** * Calculates the division (/) of this rational number and the specified argument. * *This is functionally identical to
* this.divide(BigRational.valueOf(value))
* but slightly faster.
The result has no loss of precision.
* * @param value the int value to divide (0 is not allowed) * @return the resulting rational number * @throws ArithmeticException if the argument is 0 (division by zero) */ public BigRational divide(int value) { return divide(BigInteger.valueOf(value)); } /** * Returns whether this rational number is zero. * * @returntrue
if this rational number is zero (0), false
if it is not zero
*/
public boolean isZero() {
return numerator.signum() == 0;
}
private boolean isPositive() {
return numerator.signum() > 0;
}
/**
* Returns whether this rational number is an integer number without fraction part.
*
* @return true
if this rational number is an integer number, false
if it has a fraction part
*/
public boolean isInteger() {
return isIntegerInternal() || reduce().isIntegerInternal();
}
/**
* Returns whether this rational number is an integer number without fraction part.
*
* Will return false
if this number is not reduced to the integer representation yet (e.g. 4/4 or 4/2)
true
if this rational number is an integer number, false
if it has a fraction part
* @see #isInteger()
*/
private boolean isIntegerInternal() {
return denominator.compareTo(BigDecimal.ONE) == 0;
}
/**
* Calculates this rational number to the power (xy) of the specified argument.
*
* The result has no loss of precision.
* * @param exponent exponent to which this rational number is to be raised * @return the resulting rational number */ public BigRational pow(int exponent) { if (exponent == 0) { return ONE; } if (exponent == 1) { return this; } final BigInteger n; final BigInteger d; if (exponent > 0) { n = numerator.toBigInteger().pow(exponent); d = denominator.toBigInteger().pow(exponent); } else { n = denominator.toBigInteger().pow(-exponent); d = numerator.toBigInteger().pow(-exponent); } return valueOf(n, d); } /** * Finds the minimum (smaller) of two rational numbers. * * @param value the rational number to compare with * @return the minimum rational number, eitherthis
or the argument value
*/
private BigRational min(BigRational value) {
return compareTo(value) <= 0 ? this : value;
}
/**
* Finds the maximum (larger) of two rational numbers.
*
* @param value the rational number to compare with
* @return the minimum rational number, either this
or the argument value
*/
private BigRational max(BigRational value) {
return compareTo(value) >= 0 ? this : value;
}
/**
* Returns a rational number with approximatively this
value and the specified precision.
*
* @param precision the precision (number of significant digits) of the calculated result, or 0 for unlimited precision
* @return the calculated rational number with the specified precision
*/
public BigRational withPrecision(int precision) {
return valueOf(toBigDecimal(new MathContext(precision)));
}
/**
* Returns a rational number with approximatively this
value and the specified scale.
*
* @param scale the scale (number of digits after the decimal point) of the calculated result
* @return the calculated rational number with the specified scale
*/
public BigRational withScale(int scale) {
return valueOf(toBigDecimal().setScale(scale, RoundingMode.HALF_UP));
}
private static int countDigits(BigInteger number) {
double factor = Math.log(2) / Math.log(10);
int digitCount = (int) (factor * number.bitLength() + 1);
if (BigInteger.TEN.pow(digitCount - 1).compareTo(number) > 0) {
return digitCount - 1;
}
return digitCount;
}
// TODO what is precision of a rational?
private int precision() {
return countDigits(numerator.toBigInteger()) + countDigits(denominator.toBigInteger());
}
/**
* Returns this rational number as a double value.
*
* @return the double value
*/
public double toDouble() {
// TODO best accuracy or maybe bigDecimalValue().doubleValue() is better?
return numerator.doubleValue() / denominator.doubleValue();
}
/**
* Returns this rational number as a float value.
*
* @return the float value
*/
public float toFloat() {
return numerator.floatValue() / denominator.floatValue();
}
/**
* Returns this rational number as a {@link BigDecimal}.
*
* @return the {@link BigDecimal} value
*/
public BigDecimal toBigDecimal() {
int precision = Math.max(precision(), MathContext.DECIMAL128.getPrecision());
return toBigDecimal(new MathContext(precision));
}
/**
* Returns this rational number as a {@link BigDecimal} with the precision specified by the {@link MathContext}.
*
* @param mc the {@link MathContext} specifying the precision of the calculated result
* @return the {@link BigDecimal}
*/
public BigDecimal toBigDecimal(MathContext mc) {
return numerator.divide(denominator, mc);
}
@Override
public int compareTo(BigRational other) {
if (this == other) {
return 0;
}
return numerator.multiply(other.denominator).compareTo(denominator.multiply(other.numerator));
}
@Override
public int hashCode() {
if (isZero()) {
return 0;
}
return numerator.hashCode() + denominator.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof BigRational)) {
return false;
}
BigRational other = (BigRational) obj;
if (!numerator.equals(other.numerator)) {
return false;
}
return denominator.equals(other.denominator);
}
@Override
public String toString() {
if (isZero()) {
return "0";
}
if (isIntegerInternal()) {
return numerator.toString();
}
return toBigDecimal().toString();
}
/**
* Returns a plain string representation of this rational number without any exponent.
*
* @return the plain string representation
* @see BigDecimal#toPlainString()
*/
public String toPlainString() {
if (isZero()) {
return "0";
}
if (isIntegerInternal()) {
return numerator.toPlainString();
}
return toBigDecimal().toPlainString();
}
/**
* Returns the string representation of this rational number in the form "numerator/denominator".
*
* The resulting string is a valid input of the {@link #valueOf(String)} method.
* *Examples:
*BigRational.valueOf(0.5).toRationalString()
returns "1/2"
BigRational.valueOf(2).toRationalString()
returns "2"
BigRational.valueOf(4, 4).toRationalString()
returns "4/4"
(not reduced)The integer part is omitted if it is 0 (when this absolute rational number is smaller than 1).
*The fraction part is omitted it it is 0 (when this rational number is an integer).
*If this rational number is 0, then "0" is returned.
* *Example: BigRational.valueOf(3.5).toIntegerRationalString()
returns "3 1/2"
.
Useful to create numbers like 3 1/2 (= three and a half = 3.5) by calling
* BigRational.valueOf(3, 1, 2)
.
To create a negative rational only the integer part argument is allowed to be negative:
* to create -3 1/2 (= minus three and a half = -3.5) call BigRational.valueOf(-3, 1, 2)
.
The accepted string representations are:
*toString()
of {@link BigDecimal}, {@link BigInteger}, {@link Integer}, ...toString()
of {@link Double}, {@link Float} - except "Infinity", "-Infinity" and "NaN"This function calculates the first Bernoulli numbers and therefore bernoulli(1)
returns -0.5
Note that bernoulli(x)
for all odd x > 1 returns 0