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.
4 Réponses :
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.
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.
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()
Mettez à jour le calcul du montant de la tranche comme ci-dessous
decimal installmentAmount = Math.Round((balanceAfterDP / installmentCount),2);
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
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.