分数ライブラリを書いてみる

毎回毎回その場で書くのはいいとして、ベースとなるものがないと同じようなはまりを延々と繰り返すのは目に見えているので、ライブラリを書いてみる。取り敢えず良く使う分数ライブラリから。次は(あれば)直線ライブラリ。(直線ライブラリを作ろうとしたのだけれど、分数を使わないといけなくなったから先に書いたともいう。)

import java.math.BigInteger;

class Frac implements Comparable<Frac> {

  BigInteger c;
  BigInteger m;

  Frac(String c, String m) {
    this(new BigInteger(c), new BigInteger(m));
  }

  Frac(BigInteger c, BigInteger m) {

    if( m.compareTo(BigInteger.ZERO) == 0 ) {
      // implement later
      throw new Error("not a number");
    }

    if( c.compareTo(BigInteger.ZERO) == 0 ) {
      this.c = c;
      this.m = BigInteger.ONE;
      return;
    }

    BigInteger g = gcd(c, m);
    c = c.divide(g);
    m = m.divide(g);

    if( m.compareTo(BigInteger.ZERO) < 0 ) {
      c = c.negate();
      m = m.negate();
    }

  }

  // b != 0
  BigInteger gcd(BigInteger a, BigInteger b) {
    BigInteger rem = a.remainder(b);
    if( rem.compareTo(BigInteger.ZERO) == 0 ) { return b; }
    return gcd(b, rem);
  }

  Frac neg() {
    return new Frac(c.negate(), m);
  }

  Frac inv() {
    return new Frac(m, c);
  }

  Frac add(Frac f) {
    return new Frac(c.multiply(f.m).add(f.c.multiply(m)), m.multiply(f.m));
  }

  Frac sub(Frac f) {
    return add(f.neg());
  }

  Frac mul(Frac f) {
    return new Frac(c.multiply(f.c), m.multiply(f.m));
  }

  Frac div(Frac f) {
    return mul(f.inv());
  }

  public int compareTo(Frac f) {
    return c.multiply(f.m).compareTo(f.c.multiply(m));
  }

  public String toString() {
    return c + "/" + m;
  }

}


gcdはprivateアクセスにするべきだろうなぁ。実際にはBigIntegerではなくてlongくらいで精度が足りる状況で、しかもlong精度のgcdはどこかよそで既に定義されている、という状況は良くあるので、ここで定義する必要はないのだけれど、単体で動かせるようにしておくにはこんな感じかなぁと。


sub/divがadd/mulを呼んでいるのは、単にadd/mulの実装が変わり得るから。gcdを呼んで、多少約分してからにした方がいい場合もあり得る。中間オブジェクトを一時的に作るコストはほとんどない。


後は0除算をどうするかという問題が残っているけれど、分数を使うケースによって実装が変わり得るので、今のところ保留。コピペして使う予定があるわけでもないので、デバッグも保留。(コンパイルは通った。)