A completare i problemi con l'allocazione di importi in dollari su più persone
Domanda
Qual è il modo migliore per risolvere questo problema nel codice?
Il problema è che ho 2 importi in dollari (noto come una pentola), che devono essere assegnati a 3 persone. Ogni persona riceve un importo specifico che viene da due vasi e le tariffe devono essere approssimativamente lo stesso. Continuo a imbattersi in problemi di arrotondamento dove i miei assegnazioni o aggiungere fino a troppo o troppo poco.
Ecco un esempio specifico:
Pot # 1 987,654.32
Pot # 2 123,456.78
persona # 1 ottiene Importo Allocation: 345,678.89
Persona # 2 ottiene Importo Allocation: 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, # 1 persona = 307,270.13
Pot # 1, # 2 persona = 409,421.99
Pot # 1, # 3 Persona = 270,962.21
Pot # 1 Totale = 987,654.33 (1 centesimo off)
Pot # 2, # 1 persona = 38,408.76
Pot # 2, Persona # 2 = 51,177.74
Pot # 2, # 3 persona = 33,870.27
Pot # 2 Totale = 123,456.77 (1 centesimo off)
I totali Pot devono corrispondere i totali originali.
Credo di mancare qualcosa o ci può essere un passo in più che ho bisogno di prendere. Penso di essere sulla strada giusta.
Qualsiasi aiuto sarebbe molto apprezzato.
Soluzione
Questo accade in calcoli finanziari molto quando arrotondamento al centesimo più vicino. Nessuna quantità di tweaking le singole operazioni di arrotondamento algoritmo funziona per tutti i casi.
Bisogna avere un accumulatore che traccia l'importo assegnato dopo l'operazione di arrotondamento e la distribuzione. Alla fine degli stanziamenti, si controlla l'accumulatore contro i risultati effettivi (sommati) e distribuire il centesimo avanzi.
Nell'esempio matematica di seguito, se si prende 0.133 e intorno a 0,13 e aggiungere 3 volte si ottiene un centesimo di meno che se si aggiungono 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
Altri suggerimenti
Hai provato conntrolling il comportamento di arrotondamento con l'argomento MidpointRounding?
public static decimal Round( decimal d, MidpointRounding mode )
+1 per 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 che la quantità di destinazione - in questo caso, si necessario sottrarre denaro da uno o più degli importi assegnati.
È inoltre necessario garantire che non si finisce per sottrarre un centesimo da un importo assegnato di $ 0.00 (nel caso in cui si assegnano una quantità molto piccola tra un gran numero di destinatari).
Cosa fare quando divide il denaro è un problema perenne. Martin Fowler offre alcuni commenti qui (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. Faremo che per un array di fondi, tale che la somma della matrice è uguale alla quantità originale, e il valore originario viene distribuito equamente tra gli elementi della matrice. Abbastanza in questo senso significa quelli all'inizio ottenere i centesimi 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;
}
Sicuramente il Math.round.
Vorrei suggerire non arrotondando il risultato del calcolo, ma se è necessario visualizzare, poi turno contro centesimo più vicino. Oppure si può usare pochi centesimi come minimo denominatore, quindi durante la visualizzazione, dividere tutto per 100.