倭算数理研究所

科学・数学・学習関連の記事を、「倭マン日記」とは別に書いていくのだ!

倭マン's Math (7) : FractionField, Fraction (org.apache.commons.math.fraction パッケージ)

今回は有理数体を表す FractionField クラスと、その体の元で分数(有理数)を表す Fraction クラスにメソッドを追加していきます。

インスタンス生成

まずは Fraction オブジェクトのインスタンス生成。 (int の)配列や String, Number オブジェクトに追加した frac() メソッドや Integer に追加した over() メソッドで簡単にインスタンスが生成できます:

@GrabResolver('http://www5.ocn.ne.jp/~coast/repo/')
@Grab('org.waman.math:wamans-math:0.0.1')

WamansMath.enhance(CommonsMathPackage.FRACTION)

// int / Integer の配列からインスタンス生成
assert [1, 2].frac() == new Fraction(1, 2)

// int の over() メソッドからインスタンス生成
assert 1.over(2) == [1, 2].frac()

// String / GString からインスタンス生成
assert '1 / 3'.frac() == [1, 3].frac()
assert "1 / 4".frac() == [1, 4].frac()

// Number オブジェクトの List からインスタンス生成
assert [2].frac() == [2, 1].frac()
assert [3d].frac() == [3, 1].frac()
assert [0.33333333333333333d, 10].frac() == [1, 3].frac()
assert [0.33333333333333333d, 0.01d, 10].frac() == [1, 3].frac()

Number オブジェクトの List に対して frac() メソッドを呼び出すと、各オブジェクトの型に対応した Fraction クラスのコンストラクタが呼び出されます。

メソッドの使用サンプル

次は単項演算と2項演算。 基本的に演算子オーバーロードに関連するメソッドを追加しています:

@GrabResolver('http://www5.ocn.ne.jp/~coast/repo/')
@Grab('org.waman.math:wamans-math:0.0.1')

WamansMath.enhance(CommonsMathPackage.FRACTION)

assert FractionField.instance.getMinusOne() == Fraction.MINUS_ONE

def f12 = [1, 2].frac()
def f13 = [1, 3].frac()

// 単項演算
assert +f12 == [1, 2].frac()
assert -f12 == [-1, 2].frac()
assert ~f12 == 2.frac()    // ~(bitwiseNegate) は逆数を返すようにしてます。

// Fraction 同士の2項演算
assert f12 + f13 == [5, 6].frac()
assert f12 - f13 == [1, 6].frac()
assert f12 * f13 == [1, 6].frac()
assert f12 / f13 == [3, 2].frac()

// Fraction と int / Integer との2項演算
assert f12 + 1 == [3, 2].frac()
assert f12 - 1 == [-1, 2].frac()
assert f12 * 3 == [3, 2].frac()
assert f12 / 2 == [1, 4].frac()
assert f12 ** 2 == [1, 4].frac()

// int / Integer と Fraction との2項演算
assert 1 + f12 == [3, 2].frac()
assert 1 - f12 == [1, 2].frac()
assert 3 * f12 == [3, 2].frac()
assert 2 / f12 == 4.frac()

「分数+分数」、「分数+整数」だけでなく、「整数+分数」の計算もできます(実は「double + 分数」もできます)。

フォーマット

最後はフォーマット。 FractionFormat#parse() の機能を、FractionFormat オブジェクトを生成せずに使用できるようにしてます:

@GrabResolver('http://www5.ocn.ne.jp/~coast/repo/')
@Grab('org.waman.math:wamans-math:0.0.1')

WamansMath.enhance(CommonsMathPackage.FRACTION)

// 文字列から Fraction オブジェクトを読み込む
assert Fraction.parse('1 / 3') == [1, 3].frac()

ロケールなどの詳細を設定したい場合はこのメソッドを使えないので、通常の FractionFormat オブジェクトを使用してください。

メソッド追加スクリプト

最後に Groovy のメタクラスを使ったメソッド追加スクリプト。 例によって詳細はあまり気にせずに。

        // FractionField へのメソッド追加
        FractionField.metaClass.getMinusOne = { -> Fraction.MINUS_ONE }
        
        // Fraction へのメソッド追加
        Fraction.metaClass.define{
            isMinusOne = { -> delegate == _1 }

            // 単項演算
            negative = { -> negate() }
            bitwiseNegate = { -> reciprocal() }
            previous = { -> delegate.subtract(Fraction.ONE) }
            next = { -> delegate.add(Fraction.ONE) }

            // 2項演算
            div = { Fraction arg -> divide(arg) }

            plus = { int arg -> add(arg) }
            minus = { int arg -> subtract(arg) }
            div = { int arg -> divide(arg) }
            power = { int n ->
                return new Fraction(
                        MathUtils.pow((int)getNumerator(), n),
                        MathUtils.pow((int)getDenominator(), n))
            }

            plus = { double arg -> add(new Fraction(arg)) }
            minus = { double arg -> subtract(new Fraction(arg)) }
            multiply = { double arg -> multiply(new Fraction(arg)) }
            div = { double arg -> divide(new Fraction(arg)) }

            // 型変換
            asType = { Class type ->
                switch(type){
                    case Fraction:
                        return delegate
                    case double:
                        return doubleValue()
                    case Double:
                        return Double.valueOf((double)doubleValue())
                    case String:
                        return toString()
                }
            }
        }

        // Fraction へのメソッド追加(static メソッド)
        def format = new FractionFormat()
        Fraction.metaClass.'static'.parse = { String arg -> format.parse(arg) }

Fraction, FractionField クラスへのメソッド追加はこんな感じですが、これだけだと「分数 + 整数」の計算はできても「整数+分数」の計算ができないので、Integer クラスにもメソッド追加をして「整数+分数」の計算もできるようにしましょう(Double にも同様のメソッドを追加しています)。 また、Integer, Double, String, List に frac() メソッドを追加して、Fraction オブジェクトを簡単に生成できるようにもしておきます:

        // int / Integer, double / Double との演算
        [Integer.metaClass, Double.metaClass].each{ mc ->
            mc.define{
                frac = { -> new Fraction(delegate)}

                plus = { Fraction arg -> frac().add(arg) }
                minus = { Fraction arg -> frac().subtract(arg) }
                multiply = { Fraction arg -> frac().multiply(arg) }
                div = { Fraction arg -> frac().divide(arg) }
            }
        }

        Integer.metaClass.over = { int i -> new Fraction((int)delegate, i) }
        String.metaClass.frac = { -> format.parse((String)delegate) }
        List.metaClass.frac = { -> new Fraction(*delegate) }

Javaによるアルゴリズム事典

Javaによるアルゴリズム事典

数学ガール フェルマーの最終定理 (数学ガールシリーズ 2)

数学ガール フェルマーの最終定理 (数学ガールシリーズ 2)