在C#中使用泛型创建数学库

在C#中使用泛型创建数学库

Creating a Math library using Generics in C#

是否有使用泛型创建不依赖于存储数据的基本类型的数学库的可行方法?

换句话说,假设我要编写一个Fraction类。 分数可以用两个整数或两个双精度数或其他形式表示。 重要的是基本的四个算术运算都已定义好。 因此,我希望能够编写Fraction frac = new Fraction(1,2)和/或Fraction frac = new Fraction(0.1, 1.0)

不幸的是,没有代表四个基本操作(+,-,*,/)的接口。 有没有人找到可行,可行的方法来实现这一目标?


这是一种相对简单的抽象操作员的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
    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-differentiation/


我相信这可以回答您的问题:

http://www.codeproject.com/KB/cs/genericnumerics.aspx


这是泛型类型带来的一个细微问题。假设一个算法涉及除法,例如说用高斯消元法求解方程组。如果您输入整数,将会得到错误的答案,因为您将执行整数除法。但是,如果您传递的双参数发生时具有整数值,那么您将得到正确的答案。

和Cholesky分解一样,平方根也会发生同样的事情。分解整数矩阵会出错,而分解具有整数值的双精度矩阵会很好。


首先,您的类应将泛型参数限制为基元(公共类Fraction其中T:struct,new())。

其次,您可能需要创建隐式转换重载,以便您可以处理从一种类型到另一种类型的转换,而无需编译器哭泣。

第三,当合并不同类型的分数时,您也可以使四个基本运算符重载,以使接口更加灵活。

最后,您必须考虑如何处理上溢和下溢。一个好的库在处理溢出方面将非常明确。否则,您将无法相信不同分数类型的运算结果。


推荐阅读