演算子オーバーロード

以下の実装はすべてのユニットテストに合格する。設計上の判断はコードコメントとして記載されている。

この構造体の関数の一部は、より効率的に実行するように実装することができる。さらに、分子と分母を正規化することも有益だろう。例えば、値20と60をそのまま保持する代わりに、これらの値を最大公約数で割り、分子と分母を1と3として保存することができる。そうしないと、オブジェクトに対するほとんどの操作で、分子と分母の値が増加してしまう。

import std.exception;
import std.conv;

struct Fraction {
    long num;  // 分子
    long den;  // 分母

    /* 便宜上、コンストラクタは分母にデフォルト値
     * 1を使用する。 */
    this(long num, long den = 1) {
        enforce(den != 0, "The denominator cannot be zero");

        this.num = num;
        this.den = den;

        /* 分母が常に正であることを保証することで
         * 一部の演算子関数の定義を簡略化できる。
         * */
        if (this.den < 0) {
            this.num = -this.num;
            this.den = -this.den;
        }
    }

    /* 一項 -: この分数の負の値を返す。 */
    Fraction opUnary(string op)() const
            if (op == "-") {
        /* 単に匿名オブジェクトを構築して返す。
         * */
        return Fraction(-num, den);
    }

    /* ++: 分数の値を1増やす。 */
    ref Fraction opUnary(string op)()
            if (op == "++") {
        /* ここでは'this += Fraction(1)'を使用することもできた。 */
        num += den;
        return this;
    }

    /* --: 分数の値を1減らす。 */
    ref Fraction opUnary(string op)()
            if (op == "--") {
        /* ここでは'this -= Fraction(1)'を使用することもできた。 */
        num -= den;
        return this;
    }

    /* +=: 右側の分数をこの分数に加える。 */
    ref Fraction opOpAssign(string op)(Fraction rhs)
            if (op == "+") {
        /* 加算式: a/b + c/d = (a*d + c*b)/(b*d) */
        num = (num * rhs.den) + (rhs.num * den);
        den *= rhs.den;
        return this;
    }

    /* -=: 右側の分数をこの分数から引く。 */
    ref Fraction opOpAssign(string op)(Fraction rhs)
            if (op == "-") {
        /* ここで、既に定義されている演算子+=と
         * 一項演算子-を使用している。代わりに、減算
         * 式を、上記の
         * +=演算子と同様に明示的に適用することもできる。
         *
         * 減算の公式: a/b - c/d = (a*d - c*b)/(b*d)
         */
        this += -rhs;
        return this;
    }

    /* *=: 分数を右辺で乗算する。 */
    ref Fraction opOpAssign(string op)(Fraction rhs)
            if (op == "*") {
        /* 乗算式: a/b * c/d = (a*c)/(b*d) */
        num *= rhs.num;
        den *= rhs.den;
        return this;
    }

    /* /=: 分数を右辺で割る。 */
    ref Fraction opOpAssign(string op)(Fraction rhs)
            if (op == "/") {
        enforce(rhs.num != 0, "Cannot divide by zero");

        /* 除算式: (a/b) / (c/d) = (a*d)/(b*c) */
        num *= rhs.den;
        den *= rhs.num;
        return this;
    }

    /* 二項演算子 +: この分数と右辺の分数を足し合わせた結果を。
     * 生成する。 */
    Fraction opBinary(string op)(Fraction rhs) const
            if (op == "+") {
        /* 二項演算子 *: 右辺の分数をコピーし、
         * そのコピーに左辺の分数を掛ける。 */
        Fraction result = this;
        result += rhs;
        return result;
    }

    /* 二項演算子 -: 右辺の分数からこの分数を
     * 引いた結果を返す。 */
    Fraction opBinary(string op)(Fraction rhs) const
            if (op == "-") {
        /* 既に定義されている-=演算子を使用する。 */
        Fraction result = this;
        result -= rhs;
        return result;
    }

    /* 二項演算子 *: この分数と右辺の分数を乗算した結果を
     * 返す。 */
    Fraction opBinary(string op)(Fraction rhs) const
            if (op == "*") {
        /* 既に定義されている *= 演算子を使用する。 */
        Fraction result = this;
        result *= rhs;
        return result;
    }

    /* 二項演算子 /: この分数と右辺の分数を除算した結果を
     * 返す。 */
    Fraction opBinary(string op)(Fraction rhs) const
            if (op == "/") {
        /* すでに定義されている/=演算子を使用する。 */
        Fraction result = this;
        result /= rhs;
        return result;
    }

    /* 分数の値をdoubleとして返す。 */
    double opCast(T : double)() const {
        /* 単純な除算。ただし、
         * long型の値を除算すると、小数点以下の値が失われるため、
         * ここでは'num/den'と書くことはできない。
         * */
        return to!double(num) / den;
    }

    /* 並べ替え順演算子:この分数が
     * 前にある場合は負の値を、この分数が
     * 後にある場合は正の値を、両方の分数の
     * 並べ替え順が同じ場合は0を返す。 */
    int opCmp(const Fraction rhs) const {
        immutable result = this - rhs;
        /* numはlong型であるため、int型に
         * 自動的に変換することはできない; 'to'(またはキャスト)によって
         * 明示的に変換する必要がある。 */
        return to!int(result.num);
    }

    /* 等価比較: 分数が等しい場合にtrue
     * を返す。
     *
     * この型では、等価比較を定義する必要があった。
     * なぜなら、コンパイラが生成する等価比較は、
     * オブジェクトが表す実際の値に関係なく、
     * メンバーを1つずつ比較してしまうからだ。
     *
     * たとえば、Fraction(1,2)
     * とFraction(2,4)の値は0.5だが、コンパイラが生成した
     * opEqualsは、メンバーの値が異なることを理由に、これらは等しくない
     * と判断する。 */
    bool opEquals(const Fraction rhs) const {
        /* opCmpの戻り値が0であるかどうかを確認する
         * だけで十分だ。 */
        return opCmp(rhs) == 0;
    }
}

unittest {
    /* 分母が0の場合はスローする必要がある。 */
    assertThrown(Fraction(42, 0));

    /* 1/3から始めよう。 */
    auto a = Fraction(1, 3);

    /* -1/3 */
    assert(-a == Fraction(-1, 3));

    /* 1/3 + 1 == 4/3 */
    ++a;
    assert(a == Fraction(4, 3));

    /* 4/3 - 1 == 1/3 */
    --a;
    assert(a == Fraction(1, 3));

    /* 1/3 + 2/3 == 3/3 */
    a += Fraction(2, 3);
    assert(a == Fraction(1));

    /* 3/3 - 2/3 == 1/3 */
    a -= Fraction(2, 3);
    assert(a == Fraction(1, 3));

    /* 1/3 * 8 == 8/3 */
    a *= Fraction(8);
    assert(a == Fraction(8, 3));

    /* 8/3 / 16/9 == 3/2 */
    a /= Fraction(16, 9);
    assert(a == Fraction(3, 2));

    /* 型'double'で同等の値を生成しなければならない。
     *
     * doubleはすべての値を正確に表現することはできないが、
     * 1.5は例外であることに注意。そのため、このテストが
     * この時点で適用されている。 */
    assert(to!double(a) == 1.5);

    /* 1.5 + 2.5 == 4 */
    assert(a + Fraction(5, 2) == Fraction(4, 1));

    /* 1.5 - 0.75 == 0.75 */
    assert(a - Fraction(3, 4) == Fraction(3, 4));

    /* 1.5 * 10 == 15 */
    assert(a * Fraction(10) == Fraction(15, 1));

    /* 1.5 / 4 == 3/8 */
    assert(a / Fraction(4) == Fraction(3, 8));

    /* ゼロで除算した場合はスローしなければならない。 */
    assertThrown(Fraction(42, 1) / Fraction(0));

    /* 分子の小さい方が先。 */
    assert(Fraction(3, 5) < Fraction(4, 5));

    /* 分母の大きい方が先。 */
    assert(Fraction(3, 9) < Fraction(3, 8));
    assert(Fraction(1, 1_000) > Fraction(1, 10_000));

    /* 値の小さい方が先。 */
    assert(Fraction(10, 100) < Fraction(1, 2));

    /* 負の値の方が先。 */
    assert(Fraction(-1, 2) < Fraction(0));
    assert(Fraction(1, -2) < Fraction(0));

    /* 値が等しいものは、両方とも<= and >=でなければならない。  */
    assert(Fraction(-1, -2) <= Fraction(1, 2));
    assert(Fraction(1, 2) <= Fraction(-1, -2));
    assert(Fraction(3, 7) <= Fraction(9, 21));
    assert(Fraction(3, 7) >= Fraction(9, 21));

    /* 値が等しいものは、等しくなければならない。 */
    assert(Fraction(1, 3) == Fraction(20, 60));

    /* 符号も値も等しいものは、等しくなければならない。 */
    assert(Fraction(-1, 2) == Fraction(1, -2));
    assert(Fraction(1, 2) == Fraction(-1, -2));
}

void main() {
}
D
operator_overloading.solution.1

この章で述べたように、文字列ミックスインを使用して、一部の演算子の定義を結合することができる。例えば、次の定義は4つの算術演算子をカバーしている。

/* 2進演算演算子。 */
Fraction opBinary(string op)(Fraction rhs) const
    if ((op == "+") || (op == "-") ||
        (op == "*") || (op == "/")) {
   /* この分数と右側の分数から新しい分数を作成する。右側の分数(分母)を新しい分数の分母に、
    * 左側の分数(分子)を新しい分子の分子にそれぞれ適用する。 */
    Fraction result = this;
    mixin ("result " ~ op ~ "= rhs;");
    return result;
}
D