Создание библиотеки Math с помощью Дженериков в C#

28
задан Quonux 18 January 2014 в 15:13
поделиться

3 ответа

Вот способ абстрагировать операторов, который является относительно безболезненным.

    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/

29
ответ дан fryguybob 28 November 2019 в 03:32
поделиться

Я думаю, что это отвечает на ваш вопрос:

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

6
ответ дан 28 November 2019 в 03:32
поделиться

Во-первых, ваш класс должен ограничивать универсальный параметр примитивами (открытый класс Fraction, где T: struct, new ()).

Во-вторых, вам, вероятно, потребуется создать неявные перегрузки приведения , чтобы вы могли обрабатывать приведение из одного типа в другой без крика компилятора.

В-третьих, вы также можете перегрузить четыре основных оператора, чтобы сделать интерфейс более гибким при объединении дробей разных типов.

Наконец, вы должны рассмотреть, как вы справляетесь с арифметикой и недостатками. Хорошая библиотека будет предельно ясна в том, как она обрабатывает переполнения; в противном случае вы не можете доверять результатам операций различных типов дроби.

2
ответ дан 28 November 2019 в 03:32
поделиться
Другие вопросы по тегам:

Похожие вопросы: