题
是否有任何可行的方法可以使用泛型创建不依赖于选择存储数据的基本类型的数学库?
换句话说,假设我想编写一个 Fraction 类。分数可以用两个整数或两个双精度数等来表示。重要的是基本的四种算术运算都有明确的定义。所以,我希望能够写 Fraction<int> frac = new Fraction<int>(1,2)
和/或 Fraction<double> frac = new Fraction<double>(0.1, 1.0)
.
不幸的是,没有代表四个基本操作(+、-、*、/)的接口。有人找到了可行的、可行的方法来实现这一点吗?
解决方案
这是一种相对轻松的抽象操作符的方法。
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-fferiation/
其他提示
这是泛型类型带来的一个微妙问题。假设一种算法涉及除法,例如用高斯消去法来求解方程组。如果您传入整数,您将得到错误的答案,因为您将执行 整数 分配。但是,如果您传入具有整数值的双参数,您将得到正确的答案。
平方根也会发生同样的情况,如乔列斯基分解。对整数矩阵进行因式分解会出错,而对碰巧具有整数值的双精度数矩阵进行因式分解则没问题。
首先,您的类应该将通用参数限制为基元( public class Fraction where T :结构体,new())。
其次,您可能需要创建 隐式强制转换重载 因此您可以处理从一种类型到另一种类型的转换,而编译器不会崩溃。
第三,您还可以重载四个基本运算符,以使接口在组合不同类型的分数时更加灵活。
最后,您必须考虑如何处理算术溢出和下溢。一个好的库将非常明确地处理溢出的方式;否则你不能相信不同分数类型的运算结果。