33

ジェネリックスを使用して、データを格納するために選択された基本タイプに依存しない数学ライブラリを作成するための実行可能な方法はありますか?

つまり、Fractionクラスを作成したいとします。分数は、2つのintまたは2つのdoubleなどで表すことができます。重要なことは、基本的な4つの算術演算が明確に定義されていることです。だから、私は書くことができるようになりたいFraction<int> frac = new Fraction<int>(1,2)ですFraction<double> frac = new Fraction<double>(0.1, 1.0)

残念ながら、4つの基本操作(+、-、*、/)を表すインターフェイスはありません。誰かがこれを実装するための実行可能で実行可能な方法を見つけましたか?

4

5 に答える 5

32

これは、比較的簡単な演算子を抽象化する方法です。

    abstract class MathProvider<T>
    {
        public abstract T Divide(T a, T b);
        public abstract T Multiply(T a, T b);
        public abstract T Add(T a, T b);
        public abstract T Negate(T a);
        public virtual T Subtract(T a, T b)
        {
            return Add(a, Negate(b));
        }
    }

    class DoubleMathProvider : MathProvider<double>
    {
        public override double Divide(double a, double b)
        {
            return a / b;
        }

        public override double Multiply(double a, double b)
        {
            return a * b;
        }

        public override double Add(double a, double b)
        {
            return a + b;
        }

        public override double Negate(double a)
        {
            return -a;
        }
    }

    class IntMathProvider : MathProvider<int>
    {
        public override int Divide(int a, int b)
        {
            return a / b;
        }

        public override int Multiply(int a, int b)
        {
            return a * b;
        }

        public override int Add(int a, int b)
        {
            return a + b;
        }

        public override int Negate(int a)
        {
            return -a;
        }
    }

    class Fraction<T>
    {
        static MathProvider<T> _math;
        // Notice this is a type constructor.  It gets run the first time a
        // variable of a specific type is declared for use.
        // Having _math static reduces overhead.
        static Fraction()
        {
            // This part of the code might be cleaner by once
            // using reflection and finding all the implementors of
            // MathProvider and assigning the instance by the one that
            // matches T.
            if (typeof(T) == typeof(double))
                _math = new DoubleMathProvider() as MathProvider<T>;
            else if (typeof(T) == typeof(int))
                _math = new IntMathProvider() as MathProvider<T>;
            // ... assign other options here.

            if (_math == null)
                throw new InvalidOperationException(
                    "Type " + typeof(T).ToString() + " is not supported by Fraction.");
        }

        // Immutable impementations are better.
        public T Numerator { get; private set; }
        public T Denominator { get; private set; }

        public Fraction(T numerator, T denominator)
        {
            // We would want this to be reduced to simpilest terms.
            // For that we would need GCD, abs, and remainder operations
            // defined for each math provider.
            Numerator = numerator;
            Denominator = denominator;
        }

        public static Fraction<T> operator +(Fraction<T> a, Fraction<T> b)
        {
            return new Fraction<T>(
                _math.Add(
                  _math.Multiply(a.Numerator, b.Denominator),
                  _math.Multiply(b.Numerator, a.Denominator)),
                _math.Multiply(a.Denominator, b.Denominator));
        }

        public static Fraction<T> operator -(Fraction<T> a, Fraction<T> b)
        {
            return new Fraction<T>(
                _math.Subtract(
                  _math.Multiply(a.Numerator, b.Denominator),
                  _math.Multiply(b.Numerator, a.Denominator)),
                _math.Multiply(a.Denominator, b.Denominator));
        }

        public static Fraction<T> operator /(Fraction<T> a, Fraction<T> b)
        {
            return new Fraction<T>(
                _math.Multiply(a.Numerator, b.Denominator),
                _math.Multiply(a.Denominator, b.Numerator));
        }

        // ... other operators would follow.
    }

使用する型の実装に失敗すると、コンパイル時ではなく実行時にエラーが発生します (これは悪いことです)。実装の定義MathProvider<T>は常に同じになります (これも悪いことです)。C# でこれを行うのは避けて、F# またはこのレベルの抽象化により適した他の言語を使用することをお勧めします。

編集:の加算と減算の定義を修正しましFraction<T>た。もう 1 つの興味深い単純なことは、抽象構文ツリーで動作する MathProvider を実装することです。このアイデアは、自動微分などを行うことをすぐに示しています: http://conal.net/papers/beautiful-differentiation/

于 2008-09-15T15:51:16.857 に答える
7

これがあなたの質問に答えると思います:

http://www.codeproject.com/KB/cs/genericnumerics.aspx

于 2008-09-15T15:22:29.873 に答える
3

ジェネリック型に伴う微妙な問題があります。アルゴリズムに、連立方程式を解くためのガウスの消去法などの除算が含まれているとします。整数を渡すと、整数の除算を実行するため、間違った答えが返されます。しかし、整数値を持つ二重引数を渡すと、正しい答えが得られます。

コレスキー分解の場合と同じことが平方根でも起こります。整数行列の因数分解はうまくいきませんが、たまたま整数値を持つdoubleの行列の因数分解は問題ありません。

于 2010-12-09T14:40:00.260 に答える
2

まず、クラスはジェネリック パラメータをプリミティブに制限する必要があります ( T : struct, new() の public class Fraction )。

2 つ目は、おそらく暗黙のキャスト オーバーロードを作成して、コンパイラが泣くことなく、ある型から別の型へのキャストを処理できるようにする必要があることです。

第 3 に、4 つの基本的な演算子をオーバーロードして、異なる型の分数を結合するときのインターフェイスをより柔軟にすることもできます。

最後に、算術オーバーとアンダーフローの処理方法を考慮する必要があります。優れたライブラリは、オーバーフローの処理方法が非常に明示的です。そうしないと、異なる分数タイプの演算の結果を信頼できません。

于 2008-09-15T15:18:13.193 に答える