Is there any feasible way of using generics to create a Math library that does not depend on the base type chosen to store data?
In other words, let's assume I want to write a Fraction class. The fraction can be represented by two ints or two doubles or whatnot. The important thing is that the basic four arithmetic operations are well defined. So, I would like to be able to write Fraction<int> frac = new Fraction<int>(1,2)
and/or Fraction<double> frac = new Fraction<double>(0.1, 1.0)
.
Unfortunately there is no interface representing the four basic operations (+,-,*,/). Has anybody found a workable, feasible way of implementing this?
Here is a way to abstract out the operators that is relatively painless.
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. }
If you fail to implement a type that you use, you will get a failure at runtime instead of at compile time (that is bad). The definition of the MathProvider<T>
implementations is always going to be the same (also bad). I would suggest that you just avoid doing this in C# and use F# or some other language better suited to this level of abstraction.
Edit: Fixed definitions of add and subtract for Fraction<T>
. Another interesting and simple thing to do is implement a MathProvider that operates on an abstract syntax tree. This idea immediately points to doing things like automatic differentiation: http://conal.net/papers/beautiful-differentiation/
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With