Fraction.java

package com.github.dakusui.symfonion.utils;



import com.github.dakusui.symfonion.exceptions.FractionFormatException;

import java.io.Serial;
import java.io.Serializable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.github.dakusui.symfonion.exceptions.ExceptionThrower.throwFractionFormatException;
import static com.github.dakusui.valid8j.Requires.requireArgument;
import static com.github.dakusui.valid8j_pcond.forms.Predicates.isEqualTo;
import static com.github.dakusui.valid8j_pcond.forms.Predicates.not;


/**
 * A class to implement simple Fraction functions
 * there is basically a constructor (which reduces)
 */
public record Fraction(int numerator, int denominator) implements Cloneable, Serializable {
  public static final Pattern fractionPattern = Pattern.compile("([0-9]+)/([1-9][0-9]*)");
  @Serial
  private static final long serialVersionUID = 9185757132113L;

  /* some useful constant fractions */
  public static final Fraction zero = new Fraction(0, 1);
  public static final Fraction one = new Fraction(1, 1);

  public Fraction(int numerator, int denominator) {
    requireArgument(denominator, not(isEqualTo(0)));
    int n = numerator;
    int d = denominator;
    int gcd;

    while (true) {
      gcd = this.gcd(n, d);
      if (gcd == 1)
        break;
      n /= gcd;
      d /= gcd;
    }
    this.numerator = n;
    this.denominator = d;
  }

  public static Fraction parseFraction(String str) throws FractionFormatException {
    if (str == null) {
      return null;
    }
    Matcher m = fractionPattern.matcher(str);
    if (!m.matches()) {
      throw throwFractionFormatException(str);
    }
    return new Fraction(
        Integer.parseInt(m.group(1)),
        Integer.parseInt(m.group(2))
    );
  }

  @Override
  public Fraction clone() {
    Fraction ret;
    try {
      ret = (Fraction) super.clone();
    } catch (CloneNotSupportedException e) {
      throw new AssertionError(e);
    }
    return ret;
  }

  private int gcd(int a, int b) {
    int t;

    while (b != 0) {
      t = a;
      a = b;
      b = t % a;
    }
    return (a);
  }

  public double doubleValue() {
    return (1.0 * this.numerator() / this.denominator());
  }

  public int wholePortion() {
    return (int) this.doubleValue();
  }

  public Fraction fractionPortion() {
    Fraction f = new Fraction(this.wholePortion(), 1);
    return subtract(this, f);
  }

  @Override
  public boolean equals(Object anotherObject) {
    if (!(anotherObject instanceof Fraction another))
      return false;
    return this.denominator == another.denominator && this.numerator == another.numerator;
  }

  @Override
  public String toString() {
    return (this.numerator() + "/" + this.denominator());
  }

  public static Fraction add(Fraction f1, Fraction f2) {
    int n,
        d;

    n = f1.numerator * f2.denominator + f2.numerator * f1.denominator;
    d = f1.denominator * f2.denominator;
    return new Fraction(n, d);
  }

  public static Fraction subtract(Fraction f1, Fraction f2) {
    int n,
        d;

    n = f1.numerator * f2.denominator - f2.numerator * f1.denominator;
    d = f1.denominator * f2.denominator;
    return new Fraction(n, d);
  }


  /**
   * Multiplies two fractions.
   *
   * @param f1 A fraction.
   * @param f2 Another fraction.
   * @return A multiplied result.
   */
  public static Fraction multi(Fraction f1, Fraction f2) {
    return new Fraction(f1.numerator * f2.numerator, f1.denominator * f2.denominator);
  }

  public static Fraction div(Fraction f1, Fraction f2) {
    return new Fraction(f1.numerator * f2.denominator, f1.denominator * f2.numerator);
  }

  public static int compare(Fraction f1, Fraction f2) {
    Fraction sub = subtract(f1, f2);
    return sub.numerator * sub.denominator;
  }

  public static Fraction max(Fraction f1, Fraction f2) {
    if (f1.doubleValue() - f2.doubleValue() >= 0)
      return f1;
    else
      return f2;
  }

  public static Fraction min(Fraction f1, Fraction f2) {
    if (f1.doubleValue() - f2.doubleValue() <= 0)
      return f1;
    else
      return f2;
  }

}