在 C# 中使用泛型创建数学库
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/63694/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Creating a Math library using Generics in C#
提问by Sklivvz
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)
.
换句话说,假设我想编写一个 Fraction 类。分数可以用两个整数或两个双精度数或诸如此类的东西来表示。重要的是基本的四个算术运算定义良好。所以,我希望能够编写Fraction<int> frac = new Fraction<int>(1,2)
和/或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?
不幸的是,没有代表四种基本操作(+、-、*、/)的接口。有没有人找到一种可行的、可行的方法来实现这一点?
采纳答案by fryguybob
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.
如果您未能实现您使用的类型,您将在运行时而不是在编译时出现故障(这很糟糕)。实现的定义MathProvider<T>
总是相同的(也很糟糕)。我建议您避免在 C# 中执行此操作,并使用 F# 或其他更适合此抽象级别的语言。
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/
编辑:修正了加减法的定义Fraction<T>
。另一个有趣且简单的事情是实现一个 MathProvider,它在抽象语法树上进行操作。这个想法立即指向做自动区分之类的事情:http: //conal.net/papers/beautiful-differentiation/
回答by Sklivvz
First, your class should limit the generic parameter to primitives ( public class Fraction where T : struct, new() ).
首先,您的类应该将泛型参数限制为基元( public class Fraction where T : struct, new() )。
Second, you'll probably need to create implicit cast overloadsso you can handle casting from one type to another without the compiler crying.
其次,您可能需要创建隐式转换重载,以便您可以处理从一种类型到另一种类型的转换,而编译器不会哭。
Third, you can overload the four basic operators as well to make the interface more flexible when combining fractions of different types.
第三,您还可以重载四个基本运算符,以便在组合不同类型的分数时使接口更加灵活。
Lastly, you have to consider how you are handling arithmetic over and underflows. A good library is going to be extremely explicit in how it handles overflows; otherwise you cannot trust the outcome of operations of different fraction types.
最后,您必须考虑如何处理算术上溢和下溢。一个好的库会非常明确地处理溢出;否则你不能相信不同分数类型的运算结果。
回答by Sklivvz
I believe this answers your question:
我相信这可以回答您的问题:
回答by John D. Cook
Here's a subtle problem that comes with generic types. Suppose an algorithm involves division, say Gaussian elimination to solve a system of equations. If you pass in integers, you'll get a wrong answer because you'll carry out integerdivision. But if you pass in double arguments that happen have integer values, you'll get the right answer.
这是泛型类型带来的一个微妙问题。假设一个算法涉及除法,比如高斯消元法来求解方程组。如果你传入整数,你会得到一个错误的答案,因为你将执行整数除法。但是,如果您传入具有整数值的双参数,您将得到正确的答案。
The same thing happens with square roots, as in Cholesky factorization. Factoring an integer matrix will go wrong, whereas factoring a matrix of doubles that happen to have integer values will be fine.
平方根也会发生同样的事情,就像在 Cholesky 分解中一样。对整数矩阵进行因式分解会出错,而对碰巧具有整数值的双精度矩阵进行分解会很好。