C#/linq taking sum of decimals show different results

5
Asked by : melany wijngaard 2020-10-18 21:56

I have a list of objects that I am attempting to take sum of after multiple 3 of the fields by each other. The problem is that the final result is NOT matching when I run the calculation by the following 2 cases:

1 - purchasesDeserialized.Sum(reference => reference.Price * reference.Box * reference.Qty)

2 - purchasesDeserialized.OrderBy(r => r.Box).Sum(reference => reference.Price * reference.Box * reference.Qty);

The data is identical in two cases, difference is in case #1 I do the calculation without sorting first vs in case #2 I first sort then calculate. (I expected the outcome to be the same since the sorting should not change any underlying data but rather just reorder them).

Not sure if LINQ is affecting the calculation after the OrderBy or if the issue lands on the C# decimal rounding side of things.

Full replicating code:

 using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApp1
{
    public class Purchase
    {
        public decimal Price { get; set; }
        public decimal Box { get; set; }
        public decimal Qty { get; set; }
    }
    class Program
    {
        static void Main(string[] args)
        {

            string purchases = "[{\"Box\":10.0,\"Qty\":206.000000,\"Price\":8.323300970873786407766990292},{\"Box\":10.0,\"Qty\":108.000000,\"Price\":8.333333333333333333333333333},{\"Box\":10.0,\"Qty\":46.000000,\"Price\":8.695652173913043478260869565},{\"Box\":10.0,\"Qty\":18.000000,\"Price\":24.833333333333333333333333333},{\"Box\":1.0,\"Qty\":566.000000,\"Price\":80.87985865724381625441696112},{\"Box\":1.0,\"Qty\":12.000000,\"Price\":97.46666666666666666666666667},{\"Box\":1.0,\"Qty\":72.000000,\"Price\":103.06805555555555555555555556},{\"Box\":1.0,\"Qty\":246.000000,\"Price\":81.2906504065040650406504065},{\"Box\":1.0,\"Qty\":78.000000,\"Price\":80.08333333333333333333333333},{\"Box\":10.0,\"Qty\":146.000000,\"Price\":8.030821917808219178082191782},{\"Box\":10.0,\"Qty\":178.000000,\"Price\":8.326404494382022471910112359},{\"Box\":10.0,\"Qty\":364.000000,\"Price\":8.324175824175824175824175825},{\"Box\":10.0,\"Qty\":30.000000,\"Price\":8.666666666666666666666666667},{\"Box\":10.0,\"Qty\":36.000000,\"Price\":24.5000000000000000000},{\"Box\":1.0,\"Qty\":120.000000,\"Price\":83.662500000000000000},{\"Box\":1.0,\"Qty\":332.000000,\"Price\":80.74698795180722891566265061},{\"Box\":1.0,\"Qty\":36.000000,\"Price\":78.833333333333333333333333333},{\"Box\":1.0,\"Qty\":22.000000,\"Price\":96.35909090909090909090909091},{\"Box\":1.0,\"Qty\":134.000000,\"Price\":78.149253731343283582089552239},{\"Box\":10.0,\"Qty\":26.000000,\"Price\":24.346153846153846153846153846},{\"Box\":1.0,\"Qty\":298.000000,\"Price\":97.06644295302013422818791947},{\"Box\":1.0,\"Qty\":18.000000,\"Price\":95.22777777777777777777777778},{\"Box\":10.0,\"Qty\":6.000000,\"Price\":24.166666666666666666666666667},{\"Box\":1.0,\"Qty\":82.000000,\"Price\":96.42195121951219512195121951},{\"Box\":10.0,\"Qty\":154.000000,\"Price\":8.149350649350649350649350649}]";
            var purchasesDeserialized = JsonConvert.DeserializeObject<List<Purchase>>(purchases);

            var sumRes1 = purchasesDeserialized
                .Sum(reference => reference.Price * reference.Box * reference.Qty);
            Console.WriteLine("Sum:" + sumRes1); //returns 294648.40000000000000000000000M

            var sumRes2 = purchasesDeserialized
                .OrderBy(r => r.Box)
               .Sum(reference => reference.Price * reference.Box * reference.Qty);
            Console.WriteLine("Sum after sort:" + sumRes2); //returns 294648.39999999999999999999999M
        }
    }
}

And the output:

 Sum:294648.40000000000000000000000
Sum after sort:294648.39999999999999999999999

2 Answers

0 votes
0
charlie palmer
Answer by : charlie palmer

I think the issue is as you are increasing the value of the total amount (during the Sum operation) at a same time you are decreasing the decimal maximum precision.

In this case the order of elements in the collection matters because it will affect at which point you exceed the decimal precision. The remaining elements then will be added but not precisely which in the end results in different totals.

To give an example, lets say my data type can hold up to 4 places:

 var x = 4.998;
var y = 0.002;
var z = 10.00;

x + y + z = 4.998 + 0.002 + 10.00 => 15.00;
z + x + y = 10.00 + 4.998 + 0.002 => 14.99; //(because 10.00 + 4.998 = 14.99 and there is no precision left for remaining decimal place, so its stripped)


0
پرنیا جعفری
Answer by : پرنیا جعفری

Consider for example the terms indexed from your json 0 and 22

 decimal d00 = 10M * 206.000000M * 8.323300970873786407766990292M;
decimal d22 = 10.0M * 6.000000M * 24.166666666666666666666666667M;
decimal sum = d00 + d22;

The actual results are (found using a full precision calculator)

 d00 = 17146.00000000000000000000000152 // 31
d22 =  1450.00000000000000000000000002 // 30
sum = 18596.00000000000000000000000154 // 31

but you will lose precision as you see that d00 should have 31 total digits, etc., and being limited to decimal precision, the results in c# are

 d00 = 17146.000000000000000000000002  // 29
d22 =  1450.0000000000000000000000000 // 29
sum = 18596.000000000000000000000002  // 29

so the error in this arbitrary sum is 0.00000000000000000000000046. With the way rounding goes, the errors will round up or down, depending on the order in which the addition operations go (50% chance of rounding one way or another at each step), so you will most likely end up with different results from different orders.


Related Posts
Related Posts
1
Assign variable values in parallel...
florence newman florence newman
2022-01-16  1:43am 2022-01-16 1:43am
2
Compare lists of objects where values ma...
pauline perez pauline perez
2021-04-21  5:03am 2021-04-21 5:03am
3
c# Sending a file with HttpResponseMessa...
jalisa köhler jalisa köhler
2020-06-08  7:08am 2020-06-08 7:08am
4
How to delete a row in a XAML DataGrid w...
consuelo arias consuelo arias
2020-05-21  3:02am 2020-05-21 3:02am
5
C# System.Data.SQLite details on how a r...
باران پارسا باران پارسا
2021-03-31  10:40am 2021-03-31 10:40am