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.

È stato utile?

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.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top