2
votes

Comment éviter les erreurs d'arrondi des devises avec le type de variable décimale?

Je travaille sur un calculateur de plan de paiement. J'ai changé de type de variable «double» à «décimal» pour éviter les erreurs d'arrondi, mais j'en ai toujours une quelque part. J'ai créé cet ensemble de données à utiliser pour tester mon code car il y a un reste clair:

Solde: 1 575,75 $
Acompte: 500,00 $
Solde restant: 1 075,75 $
Nombre de paiements après l'acompte: 9
Montant de l'acompte: 119,53 $ (x8)
Reste: 119,51 $

J'ai changé mes types de données de double à décimal (évidemment) J'ai essayé de réécrire le code plusieurs fois en calculant les mêmes choses de différentes manières (c'est pourquoi certaines des mathématiques à ce stade sont moins que `` minimalistes '') J'ai essayé de rendre mon code plus modulaire pour trouver l'erreur d'arrondi

// Method within my WinForms project
public void CalculateInstallmentPayments()
{
    decimal currentBalance = Convert.ToDecimal(txtBalanceInput.Text); // Current Balance
    decimal downPayment = Convert.ToDecimal(txtDownPayment.Text); // Down Payment
    decimal installmentCount = sliderRemainingPmtCount.Value; // Installment Count
    decimal balanceAfterDP = currentBalance - downPayment; // Balance After Down Payment
    decimal installmentAmount = (balanceAfterDP / installmentCount); // Installment Amount
    decimal remainderPayment = (balanceAfterDP - (installmentAmount * (installmentCount - 1))); // Final Payment (Remainder)

    // Using Rich Text box as a 'Console' for debugging
    rtxtNotate.Text = ($"Current Balance: {currentBalance.ToString()}\nDown Payment: {downPayment.ToString("C")}\n" +
        $"Installment Count: {installmentCount.ToString()}\nInstallment Amount: {installmentAmount.ToString("C")}\n" +
        $"Remainder: {remainderPayment.ToString("C")}\n");
}

Ceci est actuellement le résultat:

Solde actuel: 1575,75
Acompte: 500,00 $
Nombre de versements: 9
Montant du versement: 119,53 $
Reste: 119,53 $ - Il s'agit de l'erreur d'arrondi. Il devrait lire 119,51 $

J'ai refactorisé ce code pendant des heures et j'ai l'impression qu'il me manque quelque chose d'incroyablement simple.


1 commentaires

C'est l'une des rares bonnes raisons d'utiliser Math.Round (), vous devez calculer l'acompte à un montant qui peut réellement être payé. Précis à un centime, pas plus.


4 Réponses :


1
votes

Vous n'arrondissez pas le montant de l'acompte avant de l'appliquer. Vous obtenez donc le résultat correct sans (ou très peu) d'erreurs d'arrondi ... pour quelqu'un effectuant des paiements d'environ 119,52777777777 $

Si vous arrondissez le montant du versement avant de le stocker dans la variable installmentAmount, vous obtiendrez peut-être la réponse que vous recherchez.


1 commentaires

Je vous remercie. J'ai pris cela ci-dessus et c'était la bonne réponse, mais la formater comme vous l'avez fait est très utile. Je vous remercie.



1
votes

Le type de données decimal n'est pas un type à virgule fixe avec deux décimales, c'est un point flottant en base dix.

Vous pouvez l'utiliser pour empêcher uniquement certaines formes spécifiques d'erreurs d'arrondi qui se produisent lors de la conversion d'un nombre qui a une représentation exacte en base dix, en base deux, comme 0,1

Mais 1075.75 / 9 = 119.5277777 ...

Ce n'est pas un nombre qui peut être représenté exactement en base 10 ou en base 2, donc vous obtiendrez une erreur d'arrondi (incroyablement petite) même avec décimal .

Mais ce n'est pas vraiment votre problème. Vous arrondissez manuellement les nombres avec toString ("c") . Vous arrondissez à 2 chiffres dans la sortie, mais les calculs utilisent encore beaucoup plus de chiffres en arrière-plan.

Donc le reste ainsi que la tranche sont 119.52777777777 et tout s'additionne. Lorsque vous l'arrondissez à deux chiffres par la suite, il semble qu'il vous manque des centimes. Si vous voulez calculer le reste en utilisant l'acompte arrondi, vous devez l'arrondir vous-même, en utilisant Math.Round()


0 commentaires

0
votes

Mettez à jour le calcul du montant de la tranche comme ci-dessous

decimal installmentAmount = Math.Round((balanceAfterDP / installmentCount),2);


0 commentaires

0
votes

Vous pouvez utiliser:

decimal currentBalance = 1575.75M; // Current Balance
        decimal downPayment = 500.00M; // Down Payment
        decimal installmentCount = 9; // Installment Count
        decimal balanceAfterDP = currentBalance - downPayment; // Balance After Down Payment
        decimal installmentAmount = Math.Round((balanceAfterDP / installmentCount),2, MidpointRounding.AwayFromZero); // Installment Amount
        decimal remainderPayment = (balanceAfterDP - (installmentAmount * (installmentCount - 1))); // Final Payment (Remainder)

et vous obtiendrez la bonne valeur: 119,51


0 commentaires