【问题标题】:Creating a Math library using Generics in C#在 C# 中使用泛型创建数学库
【发布时间】:2008-09-15 15:02:16
【问题描述】:

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

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

很遗憾,没有代表四种基本操作(+、-、*、/)的接口。有没有人找到一种可行的、可行的方法来实现这一点?

【问题讨论】:

    标签: c# generics interface math


    【解决方案1】:

    这是一种抽象出相对轻松的运算符的方法。

        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&lt;T&gt; 实现的定义总是相同的(也很糟糕)。我建议您避免在 C# 中执行此操作,而使用 F# 或其他更适合此抽象级别的语言。

    编辑:修正了Fraction&lt;T&gt; 的加减法定义。 另一个有趣且简单的事情是实现一个在抽象语法树上运行的 MathProvider。这个想法立即指向做自动微分之类的事情:http://conal.net/papers/beautiful-differentiation/

    【讨论】:

    • 一般来说,我认为 MathProvider 应该做成一个接口,然后 Subtract 变成一个普通的接口方法,或者它可以实现为一个扩展方法。另一方面,这将不允许覆盖它。
    • 我想知道您的解决方案的性能...只有在所有内容都内联时才有效...
    • 还需要完全重新实现 System.Math
    • 根据下面 John D. Cook 的回答(“int 除以 int 将是整数除法并产生错误结果”),您可能需要添加 public abstract double DivideToDouble (T a, T b)
    • @fryguybob 你能举例说明如何使用它
    【解决方案2】:

    我相信这回答了你的问题:

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

    【讨论】:

    • 该解决方案和其他可用的解决方案(如使用 Emit)根本不是很干净,所以这不是我想要的。不过还是谢谢 :)
    【解决方案3】:

    这是泛型类型带来的一个微妙问题。假设一个算法涉及除法,比如说高斯消元法来求解方程组。如果你传入整数,你会得到一个错误的答案,因为你会执行 integer 除法。但是如果你传入具有整数值的双参数,你会得到正确的答案。

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

    【讨论】:

      【解决方案4】:

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

      其次,您可能需要创建implicit cast overloads,这样您就可以处理从一种类型到另一种类型的转换,而无需编译器哭泣。

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

      最后,您必须考虑如何处理算术上溢和下溢。一个好的库会非常明确地处理溢出。否则你不能相信不同分数类型的运算结果。

      【讨论】:

      【解决方案5】:

      这里的其他方法也可以,但它们对原始运算符的性能影响很大。我想我会在这里为需要最快而不是最漂亮方法的人发布这个。

      如果您想在不付出性能损失的情况下进行通用数学运算,那么不幸的是,这样做的方法是:

      [MethodImpl(MethodImplOptions.AggressiveInlining)]
      public static T IncrementToMax(T value)
      {
          if (typeof(T) == typeof(char))
              return (char)(object)value! < char.MaxValue ? (T)(object)(char)((char)(object)value + 1) : value;
       
          if (typeof(T) == typeof(byte))
              return (byte)(object)value! < byte.MaxValue ? (T)(object)(byte)((byte)(object)value + 1) : value;
      
          // ...rest of the types
      }
      

      我知道,这看起来很可怕,但是使用这种方法会生成运行速度尽可能快的代码。 JIT 将优化所有类型转换和条件分支。

      您可以在此处阅读说明和一些其他重要细节:http://www.singulink.com/codeindex/post/generic-math-at-raw-operator-speed

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-12-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多