Javaで分数のクラスを書いてみた（既約分数への変換，非Integerの分母分子に対応）

# はじめに
　本記事では`Java`で自作してみたので分数のクラスを公開させていただきます．一応テストは行いましたが欠陥があったらすみません泣

　特徴としては，次の3点を目指しました．

- 分母，もしくは分子が小数点ありでも大丈夫(本記事では分母と分子の型は`BigDecimal`です．)．
- 既約分数にできる．
- 分数同士の割り算で分母が`0`になった場合は例外が発生する．

　「この分数って割り算したら無限小数になっちゃうから，切り上げとかしないといけないよなあ．でも切り上げとかやって誤差大きくしたくないなあ．代わりに既約分数とかに変換できたら嬉しいなあ」といったニーズのある方の助力になれたら嬉しいです．
　もし読みにくいコードがあればすみません泣

# コード

こちらが分数のクラスのコードです．クラス名は`Fraction`としました．

```Fraction.java
import java.math.BigDecimal;

public class Fraction {
	private BigDecimal numerator;	// 分子
	private BigDecimal denominator;	// 分母

	public Fraction(BigDecimal numerator, BigDecimal denominator) {
		if (denominator == null) {
			throw new IllegalArgumentException("分母はnullにできません．");
		}
		if (numerator == null) {
			throw new IllegalArgumentException("分子はnullにできません．");
		}
		if (denominator.compareTo(new BigDecimal("0")) == 0) {
			throw new IllegalArgumentException("分母は0にできません．");
		}
		this.numerator = numerator;
		this.denominator = denominator;
	}

	public BigDecimal getNumerator() {
		return numerator;
	}

	public void setNumerator(BigDecimal numerator) {
		this.numerator = numerator;
	}

	public BigDecimal getDenominator() {
		return denominator;
	}
	
	public void setDenominator(BigDecimal denominator) {
		this.denominator = denominator;
	}

	// 約分する
	public Fraction getLowestTerm() {
		try {
			BigDecimal result = numerator.divide(denominator);
			return new Fraction(
				result,
				new BigDecimal("1")
			);
		} catch(ArithmeticException e) {
			int scaleByPowerOfTen = Math.max(
				numerator.scale(),
				denominator.scale()
			);
			BigDecimal tempNumerator = numerator.scaleByPowerOfTen(scaleByPowerOfTen);
			BigDecimal tempDenominator = denominator.scaleByPowerOfTen(scaleByPowerOfTen);
			BigDecimal gcm = new BigDecimal("0");
			while (gcm(tempNumerator, tempDenominator).compareTo(new BigDecimal("1")) != 0) {
				gcm = gcm(tempNumerator, tempDenominator);
				tempNumerator = tempNumerator.divide(gcm);
				tempDenominator = tempDenominator.divide(gcm);
			}
			return new Fraction(tempNumerator, tempDenominator);
		}
	}

	// 分数を足し算する
	// 分母が等しくない場合は a/b + c/d = (ad + bc)/bd を求めている
	public Fraction add(Fraction addingFraction){
		if (denominator.compareTo(addingFraction.getDenominator()) == 0) {
			return new Fraction(
				numerator.add(addingFraction.getNumerator()),
				denominator
			);
		} else {
			return new Fraction(
				( numerator.multiply(addingFraction.getDenominator()) ).add( denominator.multiply(addingFraction.getNumerator()) ),
				denominator.multiply(addingFraction.getDenominator())
			);
		}
	}

	// 分数を引き算する
	// 分母が等しくない場合は a/b - c/d = (ad - bc)/bd を求めている
	public Fraction sub(Fraction subtractingFraction) {
		if (denominator.compareTo(subtractingFraction.getDenominator()) == 0) {
			return new Fraction(
				numerator.subtract(subtractingFraction.getNumerator()),
				denominator
			);
		} else {
			return new Fraction(
				( numerator.multiply(subtractingFraction.getDenominator()) ).subtract( denominator.multiply(subtractingFraction.getNumerator()) ),
				denominator.multiply(subtractingFraction.getDenominator())
			);
		}
	}

	// 分数の掛け算する
	// a/b * c/d = ac/bd を求めている
	public Fraction mul(Fraction multiplyingFraction) {
		return new Fraction(
			numerator.multiply(multiplyingFraction.getNumerator()),
			denominator.multiply(multiplyingFraction.getDenominator())
		);
	}

	// 分数を割り算する
	// a/b / c/d = ad/bc を求めている
	public Fraction div(Fraction dividingFraction) {
		return new Fraction(
			numerator.multiply(dividingFraction.getDenominator()),
			denominator.multiply(dividingFraction.getNumerator())
		);
	}

	// BigDecimal同士の最大公約数を求める
	public static BigDecimal gcm(BigDecimal a, BigDecimal b) {
		BigDecimal temp;
		while((temp = a.remainder(b)).compareTo(new BigDecimal("0")) != 0) {
			a = b;
			b = temp;
		}
		return b;
	}
}
```

　最後の`gcm()`メソッドは，実際にこちらの`Fraction`クラスをお使いになるとなったら別のクラスにお書きになるのがよろしいかと思います．

# 使用例

`Demo`クラスという`main()`メソッドが書かれたクラスがあると想定して実際に`Fraction`クラスを活用している様子を書いてみました．

```Demo.java
import java.math.BigDecimal;

public class Demo {
	public static void main(String[] args) {
		// 分数を3つ生成
		Fraction myFrac_1 = new Fraction(
			new BigDecimal("8.5"),
			new BigDecimal("1.5")
		);
		Fraction myFrac_2 = new Fraction(
			new BigDecimal("17"),
			new BigDecimal("3")
		);
		Fraction myFrac_3 = new Fraction(
			new BigDecimal("0"),
			new BigDecimal("1")
		);

		// myFrac_1にmyFrac_2を足す
		// add，sub，mul，divの書き方は同じです
		Fraction addedFrac = myFrac_1.add(myFrac_2);
		// 足し算結果は 51 / 4.5

		// myFrac_1をmyFrac_3で割る
		Fraction dividedFrac = myFrac_1.div(myFrac_3);
		// IllegalArguementExceptionが生じます．

		// myFrac_1を既約分数に変換してmyFrac_4に代入
		Fraction myFrac_4 = myFrac_1.getLowestTerm();
		// 変換結果は 17 / 3
	}
}
```

# おわりに
　`Java`で分数の計算を扱う機会があったので書いてみました。「Java 分数」や「Java Fraction」などと検索しても，既約分数に変換できるメソッドを用意している自作クラスや，分母や分子が`Integer`以外になっても大丈夫になっている自作クラスにたどり着けなかったので書いてみました．
　私の調査不足，もしくは「本記事でかかげた特徴を持つ分数のクラスを作るとまずいことがある」，「本記事でかかげた特徴を持つ分数のクラスを作っても色んな理由により意味がない」などがあったらすみません泣
　また，分母が`0`にできないとうのも結構不便だったりもするのかなと思うので，`0`割りもやりたいな，という方向けのコードも今度書いてみようと思います．
　もし需要がおありでしたらお使いください．