2009-05-29 8 views
7

Was ist der beste Weg, um dieses Problem im Code zu lösen?Rundungsprobleme bei der Zuweisung von Dollarbeträgen für mehrere Personen

Das Problem ist, dass ich 2-Dollar-Beträge (bekannt als ein Topf) haben, die 3 Personen zugeordnet werden müssen. Jede Person erhält einen bestimmten Betrag, der aus beiden Töpfen kommt und die Raten müssen ungefähr gleich sein. Ich komme immer wieder zu Rundungsproblemen, bei denen meine Zuweisungen entweder zu viel oder zu wenig ergeben.

Hier ist ein konkretes Beispiel:

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

Person # 1 bekommt Betrag Allocation: 345,678.89
Person # 2 bekommt Betrag Allocation: 460,599.73
Person # 3 bekommt Zuteilungsbetrag: 304,832.48

Meine Logik ist wie folgt (Code ist in C#):

Die Ergebnisse ich sind wie folgt:

Pot # 1, Person # 1 = 307,270.13
Pot # 1, Person # 2 = 409,421.99
Pot # 1, Person # 3 = 270,962.21
Pot # 1 Total = 987,654.33 (1 Cent off)

Pot # 2, Person # 1 = 38,408.76
Pot # 2, Person # 2 = 51,177.74
Pot # 2, Person # 3 = 33,870.27
Pot # 2 Insgesamt = 123.456,77 (1 Cent aus)

Die Pot-Summen sollten mit den ursprünglichen Summen übereinstimmen.

Ich denke, dass ich etwas vermisse oder dass es einen zusätzlichen Schritt geben muss, den ich nehmen muss. Ich denke, ich bin auf dem richtigen Weg.

Jede Hilfe würde sehr geschätzt werden.

+0

Möglicherweise möchten Sie diesen Artikel, um zu sehen, dass ich auf geschrieben, wie dies in SQL behandeln: [Financial Abrunden von Verrechnungen] (http://www.sqlservercentral.com/articles/Financial+Rounding/88067 /) –

Antwort

12

Dies passiert in Finanzberechnungen viel beim Runden auf den nächsten Cent. Keine Optimierung der einzelnen Operationen Rundungsalgorithmus funktioniert für jeden Fall.

Sie müssen einen Akkumulator haben, der den nach der Rundungs- und Verteilungsoperation zugewiesenen Betrag verfolgt. Am Ende der Zuweisungen überprüfen Sie den Akkumulator gegen die tatsächlichen Ergebnisse (summiert) und verteilen den übrig gebliebenen Penny.

In dem mathematischen Beispiel unten, wenn Sie 0,133 nehmen und es auf 0,13 runden und 3 mal addieren, erhalten Sie einen Penny weniger, als wenn Sie zuerst 0,133 3 mal addieren und dann runden.

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

Schöne Illustration. Aus diesem Grund sollten Sie in den meisten Fällen das Runden so lange wie möglich verzögern. – pseudocoder

1

Definitiv die Math.Round.

Ich würde vorschlagen, das Berechnungsergebnis nicht zu runden, aber wenn Sie anzeigen müssen, dann runden Sie zum nächsten Penny. Oder Sie können Pennies als kleinsten Nenner verwenden, also beim Anzeigen alles durch 100 teilen.

1

Ich denke, das ist genau das Problem, das Eric Evans in seinem "Domain Driven Design" Kapitel 8, S. 198-203 anspricht.

+2

Was sagte Eric Evans dann? –

+1

Können Sie einen Auszug aus dem Buch geben? – Jon

+0

Ich stimme zu, dass Evans eine großartige Arbeit geleistet hat, dieses Problem zu diskutieren. –

2

Haben Sie versucht, das Rundungsverhalten mit dem Argument MidpointRounding zu steuern?

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

+1 für Matt Spradleys Lösung.

Als zusätzlicher Kommentar zu Matt-Lösung, müssen Sie natürlich auch für den Fall erklären, wo Sie die Zuteilung Cent am Ende (oder mehr) weniger als die Zielmenge - in diesem Fall, Sie brauchen, um Geld zu subtrahieren aus einem oder mehreren der zugewiesenen Beträge.

Sie müssen auch sicherstellen, dass Sie nicht enden, einen Penny von einem zugewiesenen Betrag von 0,00 $ subtrahieren (für den Fall, dass Sie eine sehr kleine Menge unter einer großen Anzahl von Empfängern zuweisen).

1

Was zu tun ist, wenn Geld geteilt wird, ist ein beständiges Problem. Martin Fowler bietet einige Kommentare here (ich glaube, es ausführlicher in seiner tatsächlichen PoEAA Buch):

Aber Teilung ist nicht [einfach], wie wir kümmern uns um verirrten ein paar Cent zu nehmen. Wir werden das tun, indem wir ein Array von Geldern zurückgeben, so dass die Summe des Arrays gleich der ursprünglichen Menge ist und der ursprüngliche Betrag ziemlich genau zwischen den Elementen des Arrays verteilt wird. Ziemlich in diesem Sinne bedeutet, dass diejenigen am Anfang die zusätzlichen Pfennige bekommen.

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; 
    } 
Verwandte Themen