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
Santiago Trujillo
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)
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.