簡體   English   中英

在 C# 中使用 Generics 創建數學庫

[英]Creating a Math library using Generics in C#

是否有任何可行的方法使用 generics 創建一個不依賴於選擇存儲數據的基本類型的數學庫?

換句話說,假設我想寫一個分數 class。分數可以用兩個整數或兩個雙精度數或諸如此類的東西表示。 重要的是基本的四種算術運算定義明確。 所以,我希望能夠編寫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-differentiation/

我相信這回答了你的問題:

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

這是泛型類型帶來的一個微妙問題。 假設一個算法涉及除法,比如高斯消元來求解方程組。 如果你傳入整數,你會得到一個錯誤的答案,因為你將執行integer除法。 但是如果你傳入雙 arguments 恰好有 integer 值,你會得到正確的答案。

同樣的事情發生在平方根上,就像在 Cholesky 分解中一樣。 對 integer 矩陣進行因式分解會導致 go 錯誤,而對恰好具有 integer 值的雙精度矩陣進行因式分解則沒有問題。

首先,您的 class 應該將通用參數限制為原語( public class Fraction where T: struct, new() )。

其次,您可能需要創建隱式強制轉換重載,這樣您就可以在編譯器不哭的情況下處理從一種類型到另一種類型的強制轉換。

第三,您還可以重載四個基本運算符,以便在組合不同類型的分數時使接口更加靈活。

最后,您必須考慮如何處理算術溢出和下溢。 一個好的庫在如何處理溢出方面會非常明確; 否則你不能相信不同分數類型的操作結果。

這里的其他方法也可行,但它們對原始運算符的性能影響很大。 我想我會在這里為需要最快而不是最漂亮方法的人發布這個。

如果您想在不支付性能損失的情況下進行通用數學運算,那么不幸的是,這是實現它的方法:

[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

.NET 7 引入了一個新的特性——泛型數學(閱讀更多這里這里),它基於添加static abstract接口方法 此功能引入了許多接口,這些接口允許對數字類型和/或數學運算進行一般抽象:

class Fraction<T> :
    IAdditionOperators<Fraction<T>, Fraction<T>, Fraction<T>>,
    ISubtractionOperators<Fraction<T>, Fraction<T>, Fraction<T>>,
    IDivisionOperators<Fraction<T>, Fraction<T>, Fraction<T>>
    where T : INumber<T>
{
    public T Numerator { get; }
    public T Denominator { get; }

    public Fraction(T numerator, T denominator)
    {
        Numerator = numerator;
        Denominator = denominator;
    }

    public static Fraction<T> operator +(Fraction<T> left, Fraction<T> right) =>
        new(left.Numerator * right.Denominator + right.Numerator * left.Denominator,
            left.Denominator * right.Denominator);

    public static Fraction<T> operator -(Fraction<T> left, Fraction<T> right) =>
        new(left.Numerator * right.Denominator - right.Numerator * left.Denominator,
            left.Denominator * right.Denominator);

    public static Fraction<T> operator /(Fraction<T> left, Fraction<T> right) =>
        new(left.Numerator * right.Denominator, left.Denominator * right.Numerator);
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM