MSDN.WhiteKnight - Stack Overflow answers
Ответ на "Как написать метод/класс, который бы одинаково работал со всеми числовыми типами?"
Answer 901079
Обновлено: В .NET 7 появилась встроенная поддержка Generic math, так что приведенные в данной теме ухищрения становятся не нужны. Чтобы создать метод, принимающий любое число, нужно ограничить обобщенный тип интерфейсом INumber:
static T Add<T>(T x, T y) where T : INumber<T> { return x + y; }
Ответ для предыдущих версий:
Все числовые типы объединяет то, что они являются структурами и реализуют интерфейс IComparable. С этим ограничением уже можно отсечь много неподходящих типов на этапе компиляции. Не нужно использовать статические конструкторы для "валидации", они предназначены для инициализации глобального состояния, и класс, единственная задача которого - арифметические операции, вообще не должен их иметь. Проверяйте перед вычислением (или компиляцией выражения), это намного более логично.
Что касается алгоритма, есть еще один способ, который лежит на поверхности: это простой обобщенный метод с несколькими ветками в условном операторе. Может показаться, что веток будет слишком много, но на самом деле, операции сложения для многих типов по сути одинаковы и отличаются только типом, к которому приводится конечный результат. Например, операцию сложения на целом типе можно представить как операцию сложения на Decimal с последующим "сужающим" приведением к целому типу (Decimal позволяет представить все значения любых целых типов и еще оставляет некоторый запас для обработки переполнений). Аналогично, сложение на типе float можно представить как сложение на типе double с последующим преобразованием результата.
Весь набор числовых типов можно разделить на три группы:
- Беззнаковые целые. Для них формула преобразования из Decimal в конкретный тип будет выглядеть так:
y = x % 2 n
где n - размер типа в битах.
(Остаток от деления тут появляется, так как по умолчанию у нас unchecked-контекст, и переполнения не генерируют ошибку, а просто обрезаются по границе типа.)
- Знаковые целые. Для них минимальное значение равно - 0.5 * 2 n, а максимальное 0.5 * 2 n - 1. Пользуясь этим, можно вывести формулу перевода:
y = (x + 2 n * 1.5) % 2 n - 0.5 * 2 n
На самом деле, формула может выглядеть по разному, но для отлова переполнений подходит именно такой вид.
- С плавающей точкой. Ну, тут все просто, формула не нужна, так как преобразование из double в float это просто обрезка "знаков после запятой".
Реализовать это можно так:
using System; using System.Text; namespace ConsoleApp1 { public class Calculator<T> where T : struct,IComparable { static bool IsSignedInteger(Type t) { return (t == typeof(sbyte) || t == typeof(short) || t == typeof(int) || t == typeof(long)); } static bool IsUnsignedInteger(Type t) { return (t == typeof(byte) || t == typeof(ushort) || t == typeof(uint) || t == typeof(ulong)); } static bool IsReal(Type t) { return (t == typeof(float) || t == typeof(double)); } //преобразует значение из Decimal в целевой целочисленный тип public static T FromDecimal(decimal val) { //вычисляем размер типа int size = System.Runtime.InteropServices.Marshal.SizeOf(typeof(T)); //вычисляем количество элементов в целевом множестве decimal capacity = (size < 8) ? (1L << (size * 8)) : ((decimal)UInt64.MaxValue + 1); //отображаем элемент на целевое множество decimal res; if (IsUnsignedInteger(typeof(T))) { res = (val) % (capacity); return (T)Convert.ChangeType(res, typeof(T)); } else if (IsSignedInteger(typeof(T))) { res = (val + capacity * 1.5M) % (capacity) - capacity * 0.5M; return (T)Convert.ChangeType(res, typeof(T)); } else throw new NotSupportedException(typeof(T).ToString() + " is not integer type"); } //непосредственно сложение public static T Add(T A, T B) { if (IsSignedInteger(typeof(T)) || IsUnsignedInteger(typeof(T))) { return FromDecimal(Convert.ToDecimal(A) + Convert.ToDecimal(B)); } else if (IsReal(typeof(T))) { return (T)Convert.ChangeType(Convert.ToDouble(A) + Convert.ToDouble(B), typeof(T)); } else throw new NotSupportedException(typeof(T).ToString() + " is not supported, because it is not numeric type"); } } class Program { static void Main(string[] args) { unchecked { //тест сложения целых чисел Console.WriteLine("{0} {1}", Calculator<int>.Add(1000, 222), (1000 + 222)); Console.WriteLine("{0} {1}", Calculator<byte>.Add(200, 200), (byte)(200 + 200)); Console.WriteLine("{0} {1}", Calculator<sbyte>.Add(100, 100), (sbyte)(100 + 100)); Console.WriteLine("{0} {1}", Calculator<long>.Add(long.MinValue, -1), (long)(long.MinValue - 1)); //тест сложения с плавающей точкой Console.WriteLine("{0} {1}", Calculator<float>.Add((float)Math.PI, 2.2f), (float)Math.PI + 2.2f); Console.WriteLine("{0} {1}", Calculator<double>.Add(Math.PI, 2.2), Math.PI + 2.2); //этот код выдаст исключение... //Console.WriteLine("{0}", Calculator<DateTime>.Add(DateTime.Now, new DateTime(2000, 1, 1))); //Console.WriteLine("{0}", Calculator<bool>.Add(true, true)); //а этот - не скомпилируется //Console.WriteLine("{0}", Calculator<string>.Add("Саша", "Маша")); } Console.ReadKey(); } } }
Если наплевать на переполнения, то код можно значительно упростить.
Content is retrieved from StackExchange API.
Auto-generated by ruso-archive tools.