На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
Модераторы: maxim84_
  
> Финансы + C# , Работаем с денежными величинами в C#
    Эта статья рассказывает о том, как работать с денежными величинами в C# максимально точно и безболезненно, не прибегая к использованию имеющихся типов.
    Почему не следует юзать встроенные типы данных (например, вполне подходящий decimal)? Казалось бы, взял такой тип и работаешь с деньгами, как душе угодно. Но, все имеющиеся типы имеют свои погрешности, которым при работе с финансами (а в особенности со своими :) ) просто недопустимы.
    Мы создадим собственный тип данных, который будет работать исключительно с деньгами. Однако, здесь есть свои особенности - например, понятия "валюта" или "преобразование". Поэтому сначала рассмотрим теоретическую часть работу с финансами.
    Итак, что можно делать со своими деньгами? Главное, конечно же, основные арифметические действия, то бишь, сложение, вычитание, умножение, деление. Здесь, я думаю, все понятно. Следует только отметить работу с валютами (например, складывание гривень с долларами или вычитание рублей от евро), а также максимальную точность в округлениях и работе с дробями.
    Из специфических операций выделю такие: сравнение, распределение и преобразование. Со сравнением и преобразованием, я думаю, тоже все ясно, а вот о распределении поговорим подробнее.

    C помощью операции "распределение" можно разделять некоторую сумму денег на несколько пропорциональных частей, причем эти части будут составлять исходную сумму. Например, при обычном делении 2 рублей на троих человек каждый человек получит по 66 копеек. Но, сложив три раза 66 копеек, мы получим уже не 2 рубля, а только 1 рубль 98 копеек. Так поступает, например, пресловутый Microsoft Excel.
    При распределении потерянные две копейки будут отданны двум людям, при это сохранится баланс: 67 копеек + 67 копеек + 66 копеек = 2 рубля.

    После этого маленького экскурса в теорию финансов :) перейдем к практике, т.е. кодингу.
    Разработаем новый тип данных, назовем его, естественно, Money.

    Первым делом создадим основную структуру типа, конструкторы. Также добавим дополнительный конструктор, с помощью которого будем проводить внутренние операции.
    ExpandedWrap disabled
      public struct Money {
        // Внутреннее представление - количество копеек
        private long value;  ...
      }
      //Конструкторы
        public Money(double value)
         {
          this.value = (long) Math.Round(100 * value, 2);
        }
        public Money(long high, byte low)
         {
          if (low < 0 || low > 99) throw new ArgumentException();
          if (high >= 0) value = 100 * high + low;
          else value = 100 * high - low;
        }
        // Вспомогательный конструктор
        private Money(long copecks)
            { this.value = copecks; }


    Свойства:
    ExpandedWrap disabled
      // Количество рублей
        public long High
          {
       get { return value / 100; }
       }
        // Количество копеек
        public byte Low
          {
       get { return (byte) (value % 100); }
       }


    Создав костяк нашего типа данных Money, перейдем к реализации операций. Первыми будут, конечно, арифметические. Вот, например, реализация операции "умножение":
    ExpandedWrap disabled
       // Умножение - функциональная форма
        public Money Multiply(double value)
           { return new Money((long) Math.Round(this.value * value, 2)); }
        // Умножение - операторная форма
        public static Money operator*(double a, Money b)
           { return new Money((long) Math.Round(a * b.value, 2)); }
        public static Money operator*(Money a, double b)
          { return new Money((long) Math.Round(a.value * b, 2)); }

    Операции сложения, вычитания и деления делаются по аналогии.

    Перейдем к реализации специфических денежных операций. Первой будет сравнение.
    ExpandedWrap disabled
      // Пример перегруженного оператора !=
       public static bool operator!=(Money a, Money b)
           { return a.value != b.value; }
       
      // Функциональная форма сравнения
       public int CompareTo(Money r)
         {
          if (value < r.value) return -1;
          else if (value == r.value) return 0;
          else return 1;
        }


    Операция "распределение":
    ExpandedWrap disabled
       // Деление на одинаковые части
        // Количество частей должно быть не меньше 2
        public Money[] Share(uint n)
         {
          if (n < 2) throw new ArgumentException();
          Money lowResult = new Money(value / n);
          Money highResult =  
           lowResult.value >= 0 ?
               new Money(lowResult.value + 1) :
               new Money(lowResult.value - 1);
          Money[] results = new Money[n];
          long remainder = Math.Abs(value % n);
          for (long i = 0; i < remainder; i++) results[i] = highResult;
          for (long i = remainder; i < n; i++) results[i] = lowResult;
          return results;
        }
        // Деление пропорционально коэффициентам
        // Количество коэффициентов должно быть не меньше 2
        public Money[] Allocate(params uint[] ratios)
         {
          if (ratios.Length < 2) throw new ArgumentException();
          long total = 0;
          for (int i = 0; i < ratios.Length; i++) total += ratios[i];
          long remainder = value;
          Money[] results = new Money[ratios.Length];
          for (int i = 0; i < results.Length; i++)
           {
            results[i] = new Money(value * ratios[i] / total);
            remainder -= results[i].value;
          }
          if (remainder > 0)
                  for (int i = 0; i < remainder; i++) results[i].value++;
          else      for (int i = 0; i > remainder; i--) results[i].value--;
          return results;
        }


    В качестве операции "преобразование" приведу примеру для конвертировани в тип double и в строку.
    ExpandedWrap disabled
      // Преобразование в тип double
      public static implicit operator double(Money r)
           { return (double) r.value / 100; }
        public static explicit operator Money(double d)
           { return new Money(d); }
       
      // Преобразования в строку аналогично double
        public override string ToString()
           { return ((double) this).ToString(); }
          public string ToString(IFormatProvider provider)
        {
           if (provider is IMoneyToStringProvider)
            // здесь - формирование числа прописью
            return ((IMoneyToStringProvider) provider).MoneyToString(this);
          else
            // а здесь - обычный double с учетом стандартного провайдера
            return ((double) this).ToString(provider);
        }
        public string ToString(string format)
          { return ((double) this).ToString(format); }
        public string ToString(string format, IFormatProvider provider)
          { return ((double) this).ToString(format, provider); }


    Вроде бы все, но в качестве маленького штрижка можно добавить поддержку унарных операций, например ++:
    ExpandedWrap disabled
        public static Money operator++(Money r)
           { return new Money(r.value++); }


    Теперь точно все! Новый тип для точной работы с финансами готов к использованию в ваших программах!
    Использовать его можно, например, в консольной программке следующим образом:
    ExpandedWrap disabled
          static void Main(string[] args)
          {
            Money a = new Money(10, 50);
            Money b = new Money(5.5);
            double x = 3;
            Money c = new Money();
            Money[] result;
            Console.WriteLine("--- new ---");
            Console.WriteLine("a = {0}", a);
            Console.WriteLine("b = {0}", b);
            Console.WriteLine("c = {0}", c);
            Console.WriteLine("--- Add ---");
            Console.WriteLine("{0} + {1} = {2}", a, b, a.Add(b));
            Console.WriteLine("{0} + {1} = {2}", a, b, a + b);
            Console.WriteLine("--- Subtract ---");
            Console.WriteLine("{0} - {1} = {2}", a, b, a.Subtract(b));
            Console.WriteLine("{0} - {1} = {2}", a, b, a - b);
            Console.WriteLine("--- Multiply ---");
            Console.WriteLine("{0} * {1} = {2}", x, a, a.Multiply(x));
            Console.WriteLine("{0} * {1} = {2}", x, a, x * a);
            Console.WriteLine("--- Divide ---");
            Console.WriteLine("{0} / {1} = {2}", a, x, a.Divide(x));
            Console.WriteLine("{0} / {1} = {2}", a, x, a / x);
            Console.WriteLine("--- % ---");
            Console.WriteLine("{0} % {1} = {2}", a, 4, a % 4);
            Console.WriteLine("--- GetRemainder ---");
            Console.WriteLine("{0} % {1} = {2}", a, 4, a.GetRemainder(4));
            Console.WriteLine("--- Allocate ---");
            c = new Money(1);
            result = c.Allocate(1, 1, 1);
            for (int i = 0; i < result.Length; i++)
               Console.WriteLine("#{0} = {1}", i, result[i]);
            Console.WriteLine("--- ToString ---");
            Money d = (Money) 1234567.89;
            Console.WriteLine("{0} = {1}", d, d.ToString(new RoubleToStringProvider(false, false, true)));
          }
    0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
    0 пользователей:


    Рейтинг@Mail.ru
    [ Script execution time: 0,0217 ]   [ 16 queries used ]   [ Generated: 24.04.24, 20:42 GMT ]