Domanda

C'è qualche modo fattibile di usare i generics per creare una libreria Matematica che non dipende dal tipo di base scelto di memorizzare i dati?

In altre parole, supponiamo che ho intenzione di scrivere una Frazione di classe.La frazione può essere rappresentato da due interi o due camere doppie o quant'altro.La cosa importante è che la base quattro operazioni aritmetiche sono ben definiti.Così, vorrei essere in grado di scrivere Fraction<int> frac = new Fraction<int>(1,2) e/o Fraction<double> frac = new Fraction<double>(0.1, 1.0).

Purtroppo non c'è l'interfaccia che rappresentano le quattro operazioni di base (+,-,*,/).Qualcuno ha trovato una soluzione fattibile per l'attuazione di questo?

È stato utile?

Soluzione

Qui è un modo per astrarre il gli operatori che è relativamente indolore.

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

Se non si riesce a implementare un tipo che si utilizza, si otterrà un errore in fase di esecuzione, invece di in fase di compilazione (che è male).La definizione del MathProvider<T> implementazioni sta andando sempre essere la stessa (anche male).Vorrei suggerire di evitare di fare questo in C# e F# o qualche altro linguaggio più adatto a questo livello di astrazione.

Edit: Fisso definizioni di aggiungere e sottrarre per Fraction<T>.Un altro interessante e semplice cosa da fare è implementare un MathProvider che opera su un albero di sintassi astratta.Questa idea subito punti per fare le cose come differenziazione automatica: http://conal.net/papers/beautiful-differentiation/

Altri suggerimenti

Credo che questo risponda alla tua domanda:

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

Ecco un problema che viene fornito con tipi generici.Supponiamo che un algoritmo coinvolge la divisione, dire eliminazione Gaussiana per risolvere un sistema di equazioni.Se si passa in numeri interi, si otterrà una risposta sbagliata, perché si svolge integer divisione.Ma se si passa in doppio argomenti che accadono hanno valori interi, si otterrà la giusta risposta.

La stessa cosa accade con le radici quadrate, come nella fattorizzazione di Cholesky.Factoring un intero matrice andrà male, considerando che factoring una matrice di double che capita di avere valori interi andrà bene.

Primo, la classe dovrebbe limitare il parametro generico di primitive ( public class Frazione dove T :struct (nuovo) ).

Secondo, avrete probabilmente bisogno di creare cast implicito sovraccarichi così è in grado di gestire il cast da un tipo all'altro senza che il compilatore di piangere.

In terzo luogo, si può sovraccaricare i quattro operatori di base e per rendere l'interfaccia più flessibile quando si combinano le frazioni di differenti tipi.

Infine, è necessario considerare come si sta gestendo l'aritmetica finita e underflow.Una buona libreria sta per essere estremamente esplicito nella gestione di overflow;altrimenti non ti puoi fidare il risultato delle operazioni di diversi frazione tipi.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top