2009-05-29 11 views
7

¿Cuál es la mejor manera de resolver este problema en el código?Problemas de redondeo con la asignación de cantidades en dólares en varias personas

El problema es que tengo 2 cantidades en dólares (conocidas como pot), que deben asignarse a 3 personas. Cada persona recibe una cantidad específica que proviene de ambas ollas y las tasas deben ser aproximadamente las mismas. Sigo encontrando problemas de redondeo en los que mis asignaciones suman demasiado o muy poco.

Aquí está un ejemplo específico:

Pot # 1 987,654.32
Pot # 2 123,456.78

Persona # 1 obtiene Asignación Monto: 345,678.89
Persona # 2 recibe Asignación Monto: 460,599.73
Persona # 3 pone Asignación Monto: 304,832.48

Mi lógica es la siguiente (Código está en C#):

foreach (Person person in People) 
{ 
    decimal percentage = person.AllocationAmount/totalOfAllPots; 

    decimal personAmountRunningTotal = person.AllocationAmount; 

    foreach (Pot pot in pots) 
    { 
     decimal potAllocationAmount = Math.Round(percentage * pot.Amount, 2); 
     personAmountRunningTotal -= potAllocationAmount; 

     PersonPotAssignment ppa = new PersonPotAssignment(); 
     ppa.Amount = potAllocationAmount; 

     person.PendingPotAssignments.Add(ppa); 
    } 

    foreach (PersonPotAssignment ppa in person.PendingPotAssignments) 
    { 
     if (personAmountRunningTotal > 0) //Under Allocated 
     { 
      ppa.Amount += .01M; 
      personAmountRunningTotal += .01M; 
     } 
     else if (personAmountRunningTotal < 0) //Over Allocated 
     { 
      ppa.Amount -= .01M; 
      personAmountRunningTotal -= .01M; 
     } 
    } 
} 

Los resultados que recibo son los siguientes:

Pot # 1, Persona # 1 = 307,270.13
Pot # 1, Persona # 2 = 409,421.99
Pot # 1, Persona # 3 = 270,962.21
Pot # 1 total = 987,654.33 (1 centavo off)

Pot # 2, Persona # 1 = 38,408.76
Pot # 2, Persona # 2 = 51,177.74
Pot # 2, Persona # 3 = 33,870.27
Pot # 2 Total = 123,456.77 (1 céntimo de descuento)

Los totales de pot deben coincidir con los totales originales.

Creo que me puede estar perdiendo algo o puede haber un paso adicional que deba tomar. Creo que estoy en el camino correcto.

Cualquier ayuda sería muy apreciada.

+0

Es posible que desee ver este artículo que escribí sobre la manera de manejar esto en SQL: [Redondeo financiera de atribución] (http://www.sqlservercentral.com/articles/Financial+Rounding/88067 /) –

Respuesta

12

Esto ocurre mucho en los cálculos financieros al redondear al centavo más cercano. Ninguna cantidad de ajuste del algoritmo de redondeo de operaciones individuales funcionará para todos los casos.

Debe tener un acumulador que rastree la cantidad asignada después de la operación de redondeo y distribución. Al final de las asignaciones, verifica el acumulador comparándolo con los resultados reales (sumados) y distribuye el centavo restante.

En el ejemplo matemático a continuación, si toma 0.133 y lo redondea a 0.13 y agrega 3 veces obtendrá un centavo menos que si agrega 0.133 3 veces primero y luego redondo.

0.13 0.133 
0.13 0.133 
+0.13 +0.133 
_____ ______ 
0.39 0.399 -> 0.40 
+1

Bonita ilustración. Por eso, en la mayoría de los casos, debe retrasar el redondeo el mayor tiempo posible. – pseudocoder

1

Definitivamente el Math.Round.

Sugeriría no redondear el resultado del cálculo, pero si necesita mostrarlo, redondee al centavo más cercano. O puede usar centavos como el denominador más pequeño, por lo tanto al mostrarlos, divida todo por 100.

1

Creo que este es exactamente el problema que aborda Eric Evans en su "Domain Driven Design" Capítulo 8, pp. 198-203.

+2

¿Qué dijo Eric Evans entonces? –

+1

¿Puedes dar un extracto del libro? – Jon

+0

Estoy de acuerdo con que Evans hizo un gran trabajo al discutir este tema. –

2

¿Ha intentado controlar el comportamiento de redondeo con el argumento MidpointRounding?

public static decimal Round(decimal d, MidpointRounding mode) 
2

+1 para la solución de Matt Spradley.

Como comentario adicional a la solución de Matt, que, por supuesto, también hay que tener en cuenta el caso en que se termina la asignación de centavo (o más) menos que la cantidad de destino - en ese caso, es necesario restar el dinero de uno o más de los montos asignados.

También debe asegurarse de no terminar restando un centavo de una cantidad asignada de $ 0.00 (en el caso de que esté asignando una cantidad muy pequeña entre una gran cantidad de destinatarios).

1

Qué hacer al dividir dinero es un problema perenne. Martin Fowler ofrece algunos comentarios here (creo que hay más detalle en su libro real PoEAA):

Pero la división no es [directa], ya que tenemos que tener cuidado de monedas de un centavo errantes. Lo haremos devolviendo un conjunto de dineros, de modo que la suma de la matriz sea igual a la cantidad original, y la cantidad original se distribuya equitativamente entre los elementos de la matriz. Justamente en este sentido significa que aquellos al principio obtienen los centavos extra.

class Money... 
    public Money[] divide(int denominator) { 
     BigInteger bigDenominator = BigInteger.valueOf(denominator); 
     Money[] result = new Money[denominator]; 
     BigInteger simpleResult = amount.divide(bigDenominator); 
     for (int i = 0; i < denominator ; i++) { 
      result[i] = new Money(simpleResult, currency, true); 
     } 
     int remainder = amount.subtract(simpleResult.multiply(bigDenominator)).intValue(); 
     for (int i=0; i < remainder; i++) { 
      result[i] = result[i].add(new Money(BigInteger.valueOf(1), currency, true)); 
     } 
     return result; 
    } 
Cuestiones relacionadas