C# でジェネリックスを使用して Math ライブラリを作成する
質問
ジェネリックスを使用して、データを保存するために選択された基本型に依存しない数学ライブラリを作成する実現可能な方法はありますか?
つまり、Fraction クラスを書きたいとします。小数部は 2 つの int または 2 つの double などで表すことができます。重要なことは、基本的な四則演算が明確に定義されていることです。なので、書けるようになりたいです Fraction<int> frac = new Fraction<int>(1,2)
および/または Fraction<double> frac = new Fraction<double>(0.1, 1.0)
.
残念ながら、4 つの基本操作 (+、-、、/) を表すインターフェイスはありません。これを実装する実行可能かつ実行可能な方法を見つけた人はいますか?
解決
ここでは、比較的簡単に演算子を抽象化する方法を紹介します。
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/ビューティフル-ディファレンシエーション/
他のヒント
これがあなたの質問の答えになると思います:
ここで、ジェネリック型に伴う微妙な問題があります。アルゴリズムに除算が含まれるとします。たとえば、連立方程式を解くためのガウス消去法などです。整数を渡すと、次のように実行されるため、間違った答えが得られます。 整数 分割。ただし、整数値を持つ二重引数を渡すと、正しい答えが得られます。
コレスキー因数分解と同様、平方根でも同じことが起こります。整数行列の因数分解は失敗しますが、たまたま整数値を持つ double の行列の因数分解は問題ありません。
まず、クラスはジェネリック パラメーターをプリミティブ ( public class Fraction where T :構造体、 new() )。
次に、おそらく以下を作成する必要があります。 暗黙的なキャストオーバーロード そのため、コンパイラが泣くことなく、ある型から別の型へのキャストを処理できます。
3 番目に、4 つの基本演算子をオーバーロードして、異なる型の分数を組み合わせるときにインターフェイスをより柔軟にすることができます。
最後に、算術オーバーフローとアンダーフローをどのように処理するかを考慮する必要があります。優れたライブラリは、オーバーフローの処理方法を非常に明確にします。そうしないと、さまざまな分数型の演算結果を信頼できなくなります。