Как предотвратить ошибки округления валюты с типом десятичной переменной? - PullRequest
2 голосов
/ 28 мая 2019

Я работаю над калькулятором плана платежей. Я переключился с «двойных» на «десятичные» типы переменных, чтобы избежать ошибок округления, но я все еще где-то получаю их. Я придумал этот набор данных, чтобы использовать его для тестирования своего кода, потому что есть четкий остаток:

Баланс: 1 575,75 $
Авансовый платеж: $ 500,00
Оставшийся остаток: $ 1 075,75
Количество платежей после первого взноса: 9
Сумма взноса: $ 119,53 (х8)
Остаток: 119,51

Я переключил свои типы данных с двойного на десятичный (очевидно) Я пытался переписать код несколько раз, вычисляя одни и те же вещи по-разному (вот почему некоторые математические вычисления на данный момент меньше, чем «минималистские») Я попытался сделать мой код более модульным, чтобы найти ошибку округления

// 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");
}

В настоящее время это вывод:

Текущий баланс: 1575,75
Авансовый платеж: $ 500,00
Количество взносов: 9
Сумма взноса: $ 119,53
Остаток: $ 119,53 - это ошибка округления. Стоит читать 119,51

Я часами рефакторинг этого кода, и я чувствую, что мне не хватает чего-то невероятно простого.

Ответы [ 4 ]

1 голос
/ 28 мая 2019

Тип данных decimal не является типом с фиксированной точкой с двумя десятичными знаками, это базовая десятая плавающая точка.

Вы можете использовать его, чтобы предотвратить только некоторые конкретные формы ошибок округления, которые возникают при преобразовании number, который имеет точное представление в основание десять, в основание два, такое как 0,1

Но 1075.75 / 9 = 119.5277777 ...

Это не number, который может быть точно представлен в базе 10 или в базе 2, поэтому вы получите некоторую (невероятно маленькую) ошибку округления даже с decimal.

Но на самом деле это не ваша проблема. Вы вручную округляете числа с toString("c"). Вы округляете до 2 цифр в выводе, но в вычислениях все еще используется намного больше цифр на заднем плане.

Таким образом, остаток, а также взнос 119.52777777777, и все это складывается. Когда вы округляете его до двух цифр после этого, похоже, что вы пропустили центы. Если вы хотите рассчитать остаток, используя округленный взнос, вы должны округлить его самостоятельно, используя Math.Round()

1 голос
/ 28 мая 2019

Вы не округляете сумму авансового платежа до ее применения.Таким образом, вы получаете правильный результат без (или очень очень небольших) ошибок округления ... для тех, кто делает платежи в размере около 119,52777777777

Если вы округлили сумму взноса до того, как сохранить ее в переменной installmentAmount, вы можетеполучить ответ, который вы ищете.

0 голосов
/ 28 мая 2019

Вы можете использовать:

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)

, и вы получите правильное значение: 119,51

0 голосов
/ 28 мая 2019

Обновите рассчет рассрочки платежа, как показано ниже

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