Frage

Gibt es einen gangbaren Weg Generika der Verwendung eine Math-Bibliothek zu erstellen, die auf dem Basistyp hängt nicht zum Speichern von Daten ausgewählt?

Mit anderen Worten, nehmen wir an, ich eine Klasse Fraction schreiben möchten. Die Fraktion kann durch zwei ints oder zwei Doppelbetten oder Dingsbums dargestellt werden. Das Wichtigste ist, dass die grundlegenden vier arithmetischen Operationen sind gut definiert. Also, ich würde in der Lage sein mag Fraction<int> frac = new Fraction<int>(1,2) und / oder Fraction<double> frac = new Fraction<double>(0.1, 1.0) schreiben zu können.

Leider gibt es keine Schnittstelle, um die vier Grundoperationen darstellt (+, -, *, /). Hat jemand gefunden einen brauchbaren, gangbaren Weg, dies zu implementieren?

War es hilfreich?

Lösung

Hier ist eine Art und Weise zu abstrahieren die Betreiber, die relativ schmerzlos ist.

    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.
    }

Wenn Sie eine Art Verpflichtung zur Durchführung nicht, die Sie verwenden, werden Sie einen Fehler zur Laufzeit erhalten statt zum Zeitpunkt der Kompilierung (das ist schlecht). Die Definition der MathProvider<T> Implementierungen wird immer das gleiche (auch schlecht) sein. Ich würde vorschlagen, dass Sie nur in C # tun dies zu vermeiden und F # oder eine andere Sprache besser geeignet für diese Abstraktionsebene verwendet werden.

Edit: Feste Definitionen von addieren und subtrahieren für Fraction<T>. Eine weitere interessante und einfache Sache zu tun ist, ein MathProvider zu implementieren, die auf einem abstrakten Syntaxbaum arbeitet. Diese Idee sofort zeigt auf, Dinge zu tun wie die automatische Differenzierung: http://conal.net/papers/beautiful-differentiation /

Andere Tipps

Ich glaube, dies beantwortet Ihre Frage:

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

Hier ist ein kleines Problem, das mit generischen Typen kommt. Angenommen, ein Algorithmus beinhaltet Division, sagen Gaußsche Eliminations ein System von Gleichungen zu lösen. Wenn Sie in ganzen Zahlen passieren, haben Sie eine falsche Antwort bekommen, weil Sie durchführen werden integer Division. Aber wenn Sie in Doppel Argumente übergeben, die ganzzahlige Werte passieren haben, werden Sie die richtige Antwort.

Das gleiche geschieht mit Quadratwurzeln, wie in Cholesky-Faktorisierung. eine ganze Zahl Matrix Factoring wird schief gehen, während eine Matrix aus Doppel Factoring, das wird gut passieren auf ganzzahlige Werte haben.

Zuerst Ihre Klasse sollte die allgemeinen Parameter zu Primitiven (public class Fraction where T: struct, new ()) begrenzen.

Zweitens müssen Sie wahrscheinlich implizite Überlastungen werfen so können Sie von einem Typ in einen anderen ohne den Compiler Casting Griff zu weinen.

Drittens können Sie die vier Grundrechenarten sowie Überlastung der Schnittstelle flexibler zu machen, wenn Fraktionen verschiedener Arten kombiniert werden.

Schließlich müssen Sie überlegen, wie Sie Arithmetik über und Unterschreitungen sind Handling. Eine gute Bibliothek wird als äußerst explizit, wie es behandelt überläuft; sonst können Sie nicht auf das Ergebnis der von verschiedenen Brucharten vertrauen.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top