有理数(整数/整数の形で表せる数)は必ず有限小数か循環小数のいずれかで表せる。
逆に有限小数や循環小数は必ず有理数の形に表せる。
例
3/4 = 0.75
1/7 = 0.142857142857... = 0.(142857)
(循環小数の循環する部分を循環節といい、循環節の最初と最後の数字の上に点を打って表したり、
循環節を括弧でくくって表したりする。)
任意の有理数を途中で打ち切らない小数で表してみたいと思い、有理数を表すクラスを作成し、
そのメソッドの1つに「有理数から循環節表現も可能な小数表記を出す」メソッドを作成してみた。
また、ファクトリメソッドの1つとして、循環節表現を含む小数の文字列を受け取って有理数に変えるメソッドも作成した。
有理数クラスのフィールドは、
molecule:分子、long型
denominator: 分母、long型
とした。また符号要素は統一して分子が持つことにし、既約分数の形で不変クラスとした。
整数なら分母が1という形で持つことにした。
public String toDecimalString() {
// 整数ならそのまま表示
if (denominator == 1L)
return String.valueOf(molecule);
// 筆算の要領で割り算。まずは整数部分を取り出す
LinkedHashMap<Long, Long> restAndDigit = new LinkedHashMap<>();
long integer = Math.abs(molecule) / denominator;
long remind = Math.abs(molecule) % denominator;
// 既出の余りと同じ余りが出るか、割り切れるまで割り算を繰り返す
while (!restAndDigit.containsKey(remind) && remind != 0L) {
long r = remind * 10L;
restAndDigit.put(remind, r / denominator);
remind = r % denominator;
}
StringBuilder builder = new StringBuilder();
if (molecule < 0L)
builder.append('-');
builder.append(integer).append('.');
// 割り切れたなら桁をすべて並べて表示
if (remind == 0L) {
restAndDigit.values().forEach(builder::append);
return builder.toString();
}
Iterator<Map.Entry<Long, Long>> iterator = restAndDigit.entrySet().iterator();
Map.Entry<Long, Long> temp;
while (!(temp = iterator.next()).getKey().equals(remind)) {
builder.append(temp.getValue());
}
builder.append('(').append(temp.getValue());
iterator.forEachRemaining(e -> builder.append(e.getValue()));
return builder.append(')').toString();
}
public static Fraction2 parseDecimal(String str) {
Matcher matcher = Pattern.compile("(-?\\d+\\.\\d*)(\\(\\d+\\))?").matcher(str);
if (!matcher.matches())
throw new NumberFormatException("Illegal input : " + str);
BigDecimal decimal = new BigDecimal(matcher.group(1));
if (matcher.group(2) == null)
return fromDecimal(decimal);
BigInteger m1 = decimal.unscaledValue();
BigInteger d1 = TEN.pow(decimal.scale());
BigDecimal decimal2 = new BigDecimal(matcher.group().replaceAll("[\\(\\)]", ""));
BigInteger m2 = decimal2.unscaledValue();
BigInteger d2 = TEN.pow(decimal2.scale());
// BigIntegerを使って有理数にする別メソッド
return fromBigInteger(m2.subtract(m1), d2.subtract(d1));
}
もともと分母分子をBigIntegerで作っていて放置していたものを、「そこまで大きな数が分母分子に来ることはないだろう」と思ってlongに書き換えていたが、加算や乗算でオーバーフローを気にしだすと面倒だからBigIntegerを使っていたということを思い出したorz