2009-05-29 5 views
7

Qual è il modo migliore per risolvere questo problema nel codice?Problemi di arrotondamento con l'allocazione di importi in dollari su più persone

Il problema è che ho 2 importi in dollari (noto come un piatto), che devono essere assegnati a 3 persone. Ogni persona riceve un importo specifico che proviene da entrambi i piatti e le tariffe devono essere approssimativamente uguali. Continuo a riscontrare problemi di arrotondamento in cui le mie allocazioni si sommano troppo o troppo poco.

Ecco un esempio specifico:

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

persona # 1 ottiene Allocation Importo: 345,678.89
persona # 2 ottiene Allocation Importo: 460,599.73
persona # 3 ottiene Allocation Importo: 304,832.48

La mia logica è la seguente (codice è in 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; 
     } 
    } 
} 

I risultati che ottengo sono i seguenti:

Pot # 1, persona # 1 = 307,270.13
Pot # 1, Persona # 2 = 409,421.99
Pot # 1, persona # 3 = 270,962.21
pot # 1 totale = 987,654.33 (1 centesimo off)

pot # 2, persona # 1 = 38,408.76
pot # 2, persona # 2 = 51,177.74
pot # 2, persona # 3 = 33,870.27
pot # 2 Totale = 123,456,77 (1 penny off)

I totali del piatto devono corrispondere ai totali originali.

Penso che possa mancare qualcosa o potrebbe esserci un ulteriore passaggio che devo fare. Penso di essere sulla strada giusta.

Qualsiasi aiuto sarebbe molto apprezzato.

+0

Si consiglia di leggere questo articolo che ho scritto su come gestire questo in SQL: [Arrotondamento finanziaria delle assegnazioni] (http://www.sqlservercentral.com/articles/Financial+Rounding/88067 /) –

risposta

12

Questo accade nei calcoli finanziari molto quando si arrotonda al centesimo più vicino. Nessuna quantità di modifica del singolo algoritmo di arrotondamento delle operazioni funzionerà per ogni caso.

È necessario disporre di un accumulatore che tenga traccia dell'importo assegnato dopo l'arrotondamento e l'operazione di distribuzione. Alla fine delle allocazioni, controlli l'accumulatore rispetto ai risultati effettivi (sommati insieme) e distribuisci il penny rimanente.

Nell'esempio di matematica di seguito, se si prende 0.133 e si arrotonda a 0.13 e si aggiungono 3 volte si ottiene un penny in meno rispetto a se si aggiunge 0.133 3 volte prima e poi rotondo.

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

Bella illustrazione. Questo è il motivo per cui nella maggior parte dei casi è necessario ritardare l'arrotondamento il più a lungo possibile. – pseudocoder

1

Definitivamente il Math.Round.

Suggerirei di non arrotondare il risultato del calcolo, ma se è necessario visualizzare, arrotondare al centesimo più vicino. Oppure si può usare pochi centesimi come minimo denominatore, quindi durante la visualizzazione, dividere tutto per 100.

1

Penso che questo sia esattamente il problema che Eric Evans si rivolge nel suo "Domain Driven Design" capitolo 8, pp. 198-203.

+2

Che cosa ha detto Eric Evans allora? –

+1

Puoi fornire un estratto dal libro? – Jon

+0

Sono d'accordo sul fatto che Evans abbia fatto un ottimo lavoro discutendo di questo problema. –

2

Hai provato a conntrollare il comportamento di arrotondamento con l'argomento MidpointRounding?

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

+1 per la soluzione di Matt Spradley.

Come un commento aggiuntivo alla soluzione di Matt, è ovviamente necessario anche conto nel caso in cui si finisce per allocare centesimo (o più) meno rispetto all'importo obiettivo - in questo caso, è necessario sottrarre denaro da uno o più degli importi assegnati.

È inoltre necessario assicurarsi di non sottrarre un centesimo da un importo assegnato di $ 0,00 (nel caso in cui si stia assegnando una quantità molto piccola a un numero elevato di destinatari).

1

Che cosa fare quando si dividono i soldi è un problema perenne. Martin Fowler offre alcuni commenti here (penso che ci sia più in dettaglio nel suo attuale PoEAA libro):

Ma la divisione non è [semplice], come abbiamo a prendersi cura di penny erranti. Lo faremo restituendo un array di risorse, in modo tale che la somma dell'array sia uguale alla quantità originale e l'importo originale sia distribuito equamente tra gli elementi dell'array. Abbastanza in questo senso significa che all'inizio ottengono i penny in più.

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; 
    } 
Problemi correlati