Ошибка из-за точности двойного типа в C++

Я написал класс для расчета суммы денег для меня. Ниже код вкратце.

class RMB
{
private:
    int yuan;
    int jiao;
    int fen;
    bool mark;
public:
RMB(int yu, int ji, int fe, bool mar = true)
    {
        yuan = yu;
        jiao = ji;
        fen = fe;
        mark = mar;
    }
    RMB(double money)
    {
        int money1 = int(money * 100);
        yuan = money1 / 100;
        fen = money1 % 10;
        jiao = (money1 - yuan * 100 - fen)/10;
        if (money < 0) mark = 0;
        else mark = 1;
    }
    operator double()
    {
        double money = yuan + double(jiao) / 10 + double(fen) / 100;
        if (mark == false) return -money;
        return money;
    }
};
int main()
{
    RMB a(1,2,3);RMB b(2,3,4);
cout << "a + c = " << RMB(a + c) << endl;//assume I have override "<<" and ">>"

cout << "a - b = " << RMB(a - b) << endl;
}

Но когда я тестировал свой код, случилось так:

a + c = 2 yuan 4 jiao 5 fen

a - b = -1 yuan -1 jiao 0 fen

Я проверяю это в VS2015, я отладил его, и я вижу точное значение double(a-b) -1.199999998. Итак, как я могу исправить эту ошибку и что я могу сделать, чтобы избежать такой ошибки?

3 ответа

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

Если они есть у вашего компилятора, используйте std::uint64_t как тип, или std::int64_t если вам нужна концепция отрицательной суммы. Это более чем достаточно для мирового ВВП, выраженного в зимбабвийских долларах. Вы могли бы даже обернуть это в class для проверки будущего.

C++ намеренно не предоставляет десятичный тип "из коробки".

RMB(double money)
{
    int money1 = int(money * 100);

Если деньги отрицательны, значит money1 тоже!

    yuan = money1 / 100;
    fen = money1 % 10;
    jiao = (money1 - yuan * 100 - fen)/10;

Так будут ваши участники!

    if (money < 0)
        mark = 0;
    else
        mark = 1;

И вы храните знак дополнительно (для использования в operator double):

return mark ? money : -money;

Предположим, что вы должны сделать своих членов положительными вместо этого (и я бы предпочел неподписанных членов тогда):

RMB(double money)
    : mark(money >= 0)
{
    unsigned int money1 = (int)((mark ? money : -money) * 100);
    // ...
}

Я бы порекомендовал это прочитать: https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/

Для более глубокого изучения предмета я рекомендую: "Что должен знать каждый компьютерный специалист об арифметике с плавающей точкой"

Другие вопросы по тегам