在C#中使用generics创buildmath库
有没有可行的方法使用generics创build一个math库,不依赖于select存储数据的基本types?
换句话说,我们假设我想写一个Fraction类。 这个分数可以用两个或两个双打或者什么来表示。 重要的是基本的四个算术运算是明确的。 所以,我希望能够写入Fraction<int> frac = new Fraction<int>(1,2)
和/或Fraction<double> frac = new Fraction<double>(0.1, 1.0)
。
不幸的是,没有界面代表四个基本操作(+, – ,*,/)。 有没有人find一个可行,可行的方式来实现这一点?
这是一个抽象出相对无痛的运营商的方法。
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. }
如果你不能实现你使用的types,你将在运行时而不是在编译时(这是不好的)得到一个失败。 MathProvider<T>
实现的定义总是相同的(也是不好的)。 我build议你只是避免在C#中做这个,并使用F#或其他更适合这个抽象级别的语言。
编辑:修正Fraction<T>
加减的定义。 另一个有趣而简单的事情是实现一个在抽象语法树上运行的MathProvider。 这个想法立即指向做自动分化的事情: http : //conal.net/papers/beautiful-differentiation/
这是genericstypes的一个微妙的问题。 假设一个algorithm涉及到分解,比如高斯消元来求解一个方程组。 如果你传入整数,你会得到一个错误的答案,因为你会进行整数除法。 但是,如果你传递了具有整数值的双重参数,你会得到正确的答案。
在Cholesky分解中,同样的事情发生在平方根上。 分解一个整数matrix将会出错,而对具有整数值的双精度matrix进行因式分解将会很好。
首先,你的类应该将generics参数限制为基本types(public class Fraction,其中T:struct,new())。
其次,您可能需要创build隐式转换重载,以便在不编译器哭泣的情况下处理从一种types到另一种types的转换。
第三,您可以重载四个基本的操作符,以便在组合不同types的分数时使界面更加灵活。
最后,你必须考虑如何处理算术和下溢。 一个好的图书馆将如何处理溢出是非常明确的; 否则你不能相信不同分数types操作的结果。