Ratio.java
package org.cardanofoundation.rewards.calculation.util;
import java.math.BigDecimal;
import java.math.BigInteger;
/**
* Exact rational arithmetic for ledger calculations that need Haskell
* {@code Rational}-style behavior and a single explicit floor at the end.
*/
public final class Ratio {
public static final Ratio ZERO = new Ratio(BigInteger.ZERO, BigInteger.ONE);
public static final Ratio ONE = new Ratio(BigInteger.ONE, BigInteger.ONE);
private final BigInteger numerator;
private final BigInteger denominator;
private Ratio(BigInteger numerator, BigInteger denominator) {
if (denominator.signum() == 0) {
throw new IllegalArgumentException("Ratio denominator must not be zero");
}
if (numerator.signum() == 0) {
this.numerator = BigInteger.ZERO;
this.denominator = BigInteger.ONE;
return;
}
BigInteger normalizedNumerator = numerator;
BigInteger normalizedDenominator = denominator;
if (normalizedDenominator.signum() < 0) {
normalizedNumerator = normalizedNumerator.negate();
normalizedDenominator = normalizedDenominator.negate();
}
BigInteger gcd = normalizedNumerator.gcd(normalizedDenominator);
this.numerator = normalizedNumerator.divide(gcd);
this.denominator = normalizedDenominator.divide(gcd);
}
public static Ratio of(BigInteger numerator) {
return of(numerator, BigInteger.ONE);
}
public static Ratio of(BigInteger numerator, BigInteger denominator) {
return new Ratio(numerator, denominator);
}
public static Ratio from(BigDecimal value) {
BigDecimal normalized = value.stripTrailingZeros();
BigInteger numerator = normalized.unscaledValue();
int scale = normalized.scale();
if (scale < 0) {
return of(numerator.multiply(BigInteger.TEN.pow(-scale)), BigInteger.ONE);
}
return of(numerator, BigInteger.TEN.pow(scale));
}
public BigInteger numerator() {
return numerator;
}
public BigInteger denominator() {
return denominator;
}
public Ratio multiply(Ratio other) {
return of(numerator.multiply(other.numerator), denominator.multiply(other.denominator));
}
public Ratio subtract(Ratio other) {
return of(numerator.multiply(other.denominator).subtract(other.numerator.multiply(denominator)),
denominator.multiply(other.denominator));
}
public BigInteger floor() {
return floor(numerator, denominator);
}
public BigInteger multiplyAndFloor(BigInteger value) {
return floor(value.multiply(numerator), denominator);
}
@Override
public String toString() {
return numerator + "/" + denominator;
}
private static BigInteger floor(BigInteger numerator, BigInteger denominator) {
BigInteger[] quotientAndRemainder = numerator.divideAndRemainder(denominator);
if (numerator.signum() < 0 && quotientAndRemainder[1].signum() != 0) {
return quotientAndRemainder[0].subtract(BigInteger.ONE);
}
return quotientAndRemainder[0];
}
}