2008-09-15 26 views
25

ジェネリックを使用してデータを保存するために選択された基本タイプに依存しない数学ライブラリを作成する方法はありますか?C#でジェネリックスを使用して数学ライブラリを作成する

つまり、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つの基本操作(+、 - 、*、/)を表すインターフェイスはありません。誰か実行可能で実現可能な方法を見つけましたか?

答えて

25

は比較的簡単であるオペレータアウト抽象化への道です。あなたが使用するタイプを実装するために失敗した場合

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>の加算と減算の固定定義。 興味深く簡単なことは、抽象構文ツリーで動作するMathProviderを実装することです。このアイデアはすぐに自動判別のようなことを指します:http://conal.net/papers/beautiful-differentiation/

+0

きれいな素敵な:) –

+1

私は一般的な方法で私はMathProviderは、インターフェイスに作成し、通常のインターフェイスメソッドに引くか、または拡張メソッドとして実装することができると思う。一方、それを上書きすることはできません。 – dalle

+0

私はあなたのソリューションのパフォーマンスについて疑問に思っています... すべてがインライン展開されている場合にのみ有効です... –

1

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

第2に、implicit cast overloadsを作成する必要があります。そのため、コンパイラが泣かなくても1つのタイプから別のタイプへのキャストを処理できます。

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

最後に、算術のオーバーフローとアンダーフローの処理方法を検討する必要があります。良いライブラリはオーバーフローをどのように処理するかについて非常に明示的になります。そうでなければ、異なる分数型の演算の結果を信頼することはできません。

+1

問題は、構造体に加算演算子が定義されていないため、そのような合計を行うことすらできないということです。 – Sklivvz

+0

http://msdn.microsoft.com/en-us/library/aa691324(VS.71)。"aspx"ユーザー定義の実装は、演算子の宣言をクラスと構造体に含めることで導入できます。 – Will

6

私はこれがあなたの質問に答えると考えている。ここで

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

+1

このソリューションとEmitのような利用可能なソリューションは、まったくクリーンではありません。しかし、とにかくありがとう:) – Sklivvz

2

ここでは、ジェネリックタイプに伴う微妙な問題があります。あるアルゴリズムが除算を含むとしましょう。たとえば、ガウスの消去を使って方程式の系を解くとします。整数を渡すと、整数除算が実行されるため、間違った答えが返されます。しかし、整数値を持つ倍精度引数を渡すと、正しい答えが得られます。

コレスキー分解の場合と同じことが、平方根でも起こります。整数行列を因数分解することは間違っていますが、整数値を持つ二倍行列を因数分解することは問題ありません。

関連する問題