Tengo una lista de objetos de los que intento sumar después de múltiples 3 de los campos entre sí. El problema es que el resultado final NO coincide cuando ejecuto el cálculo en los siguientes 2 casos:
1 - purchasesDeserialized.Sum(reference => reference.Price * reference.Box * reference.Qty)
2 - purchasesDeserialized.OrderBy(r => r.Box).Sum(reference => reference.Price * reference.Box * reference.Qty);
Los datos son idénticos en dos casos, la diferencia es que en el caso n. ° 1 hago el cálculo sin ordenar primero frente al caso n. ° 2, primero ordeno y luego calculo. (Esperaba que el resultado fuera el mismo, ya que la clasificación no debería cambiar ningún dato subyacente, sino simplemente reordenarlos).
No estoy seguro de si LINQ está afectando el cálculo después de OrderBy o si el problema aterriza en el lado del redondeo decimal de C#.
Código de replicación completo:
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 } } }
Y la salida:
Sum:294648.40000000000000000000000 Sum after sort:294648.39999999999999999999999
Creo que el problema es que está aumentando el valor de la cantidad total (durante la operación Sum) al mismo tiempo que está disminuyendo la precisión máxima decimal.
En este caso, el orden de los elementos en la colección es importante porque afectará en qué punto se excede la precisión decimal. Los elementos restantes luego se agregarán, pero no precisamente, lo que al final da como resultado diferentes totales.
Para dar un ejemplo, digamos que mi tipo de datos puede contener hasta 4 lugares:
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)
Considere, por ejemplo, los términos indexados desde su json 0 y 22
decimal d00 = 10M * 206.000000M * 8.323300970873786407766990292M; decimal d22 = 10.0M * 6.000000M * 24.166666666666666666666666667M; decimal sum = d00 + d22;
Los resultados reales son (encontrados usando una calculadora de precisión completa )
d00 = 17146.00000000000000000000000152 // 31 d22 = 1450.00000000000000000000000002 // 30 sum = 18596.00000000000000000000000154 // 31
pero perderá precisión al ver que d00
debe tener 31 dígitos en total, etc., y al estar limitado a la precisión decimal
, los resultados en C# son
d00 = 17146.000000000000000000000002 // 29 d22 = 1450.0000000000000000000000000 // 29 sum = 18596.000000000000000000000002 // 29
entonces el error en esta suma arbitraria es 0.000000000000000000000000046. Con la forma en que se realiza el redondeo, los errores se redondearán hacia arriba o hacia abajo, según el orden en que se realicen las operaciones de suma (50% de probabilidad de redondear de una forma u otra en cada paso), por lo que lo más probable es que termine con resultados diferentes de órdenes diferentes.