У меня два значения decimal

3.10m = 3 years 10 months 
 2.8m = 2 years  8 months.

Я пытаюсь sum эти значения получить 5.9, что неверно .

ожидаемый результат должен быть

6.6m = 6 years 6 months

Может ли кто-нибудь предложить , как добиться этого с помощью TimeSpan или любым другим способом на C #. Заранее спасибо

1
Sagar Jagadesh 26 Сен 2018 в 10:57

2 ответа

Лучший ответ

Не делай этого! decimal не для year.month. Технически вы можете решить проблему и различать 3.1m и 3.10m (decimal.GetBits помогает); вот код для академического использования :

private static decimal EerieArithmetics(decimal d1, decimal d2) {
  int y1 = (int)d1;
  int m1 = (int)((d1 % 1m) * ((((decimal.GetBits(d1)[3] >> 16) & 31) == 2) ? 100m : 10m));

  int y2 = (int)d2;
  int m2 = (int)((d2 % 1m) * ((((decimal.GetBits(d2)[3] >> 16) & 31) == 2) ? 100m : 10m));

  int y = y1 + y2 + (m1 + m2) / 12;
  int m = (m1 + m2) % 12;

  return y + (m == 10 ? 0.10m : m > 10 ? m / 100.00m : m / 10.0m);
}

< Сильный > Демо :

Tuple<decimal, decimal>[] tests = new Tuple<decimal, decimal>[] {
  Tuple.Create(3.10m,  2.8m),
  Tuple.Create( 3.1m,  2.8m),
  Tuple.Create( 3.0m,  2.8m),
  Tuple.Create( 3.0m,  2.0m),
  Tuple.Create(   3m,    2m),
  Tuple.Create( 3.8m,  2.4m),
  Tuple.Create(3.10m, 3.10m),
  Tuple.Create( 2.8m,  2.2m),
  Tuple.Create(2.11m,  2.2m),
};

string report = string.Join(Environment.NewLine, tests
  .Select(test => 
     $"{test.Item1,5} + {test.Item2,5} == {EerieArithmetics(test.Item1, test.Item2),5}"));

Console.Write(report);

< Сильный > Результат :

 3.10 +   2.8 ==   6.6
  3.1 +   2.8 ==   5.9
  3.0 +   2.8 ==   5.8
  3.0 +   2.0 ==     5
    3 +     2 ==     5
  3.8 +   2.4 ==     6
 3.10 +  3.10 ==   7.8
  2.8 +   2.2 ==  4.10
 2.11 +   2.2 ==   5.1

Надеюсь, я напугал вас от такого использования decimal. В качестве быстрого и грязного патча (если вы не можете использовать какую-либо библиотеку, кроме стандартной), вы можете попробовать класс DateTime:

 DateTime d1 = new DateTime(3, 10, 1); // 1 Oct 3 AD
 DateTime d2 = new DateTime(2, 8, 1);  // 1 Aug 2 AD

 DateTime result = d1
   .AddYears(d2.Year)
   .AddMonths(d2.Month);               // 1 Jun 6 AD

 // 6.6
 Console.Write($"{result.Year}.{result.Month}"); 
2
Dmitry Bychenko 26 Сен 2018 в 09:44

Во-первых, я бы не стал использовать такие десятичные дроби для представления значений года / месяца. Как отмечено в комментариях, вы не сможете легко отличить 1 месяц от 10 месяцев ... в то время как decimal может представлять разницу между 3.1 и 3.10, это было бы очень странное использование. Просто сохраните два значения в отдельных целых числах.

Далее, вы не можете выполнять такую арифметику с помощью TimeSpan. TimeSpan просто хранит «количество тактов» - он не обрабатывает такие идеи, как «1 месяц» или «1 год», потому что они не представляют фиксированное количество тактов.

Я бы рекомендовал вместо этого использовать мою библиотеку Noda Time. Вы бы представили эти значения как объекты Period. Вы по-прежнему не сможете напрямую сложить годы и месяцы - Period не предполагает григорианской календарной системы, а такая арифметика просто не работает для некоторых не-григорианских календарей, особенно тех, которые изменяют количество месяцев в зависимости от года. Однако вы можете легко написать свой собственный метод нормализации Period:

using System;
using NodaTime;

class Test
{
    static void Main()
    {
        Period p1 = new PeriodBuilder { Years = 3, Months = 10 }.Build();
        Period p2 = new PeriodBuilder { Years = 5, Months = -12 }.Build();
        Period sum = p1 + p2;
        Period normalized = NormalizeYearsAndMonths(sum);
        Console.WriteLine($"{normalized.Years} years; {normalized.Months} months");
    }

    static Period NormalizeYearsAndMonths(Period period)
    {
        // TODO: Handle negative years and months however you want.
        int years = period.Years;
        int months = period.Months;
        years += months / 12;
        months = months % 12;
        var builder = period.ToBuilder();
        builder.Years = years;
        builder.Months = months;
        return builder.Build();
    }
}

Теперь, конечно, вы можете просто сохранить все в виде двух целых чисел - преимущество использования Period в том, что вы можете легко добавить его в LocalDate или {{X2} } и т.д. Вы можете даже сделать это без нормализации для начала, если хотите.

5
Jon Skeet 26 Сен 2018 в 08:12