【问题标题】:Slightly different answers when changing associativity更改关联性时的答案略有不同
【发布时间】:2016-05-02 12:43:48
【问题描述】:

通过以下简单的 C++ 练习

#include <iostream>
using namespace std;
int main() 
{
    int euro, cents_v1, cents_v2, do_again;
    double price;

    do_again = 1;

    while(do_again != 0){

        cout<<"Insert price in Euro with cents"<<endl;
        cin>>price;

        euro = price;
        cents_v1 = price*100 - euro*100;
        cents_v2 = (price - euro) * 100;

        cout<<"Total: "<<euro<<" euro and "<<cents_v1<<" cents"<< endl;
        cout<<"Total: "<<euro<<" euro and "<<cents_v2<<" cents"<< endl;

        cout <<"\nDo it again? 1: yes, 0: no."<<endl;
        cin>>do_again;

    }
    return 0;

}

如果输入是,例如 31.13,你可以获得两个不同的答案:

Insert price in Euro with cents
31.13
Total: 31 euro and 13 cents
Total: 31 euro and 12 cents

Do it again? 1: yes, 0: no.

如何处理这个问题?编程中是否有规则可以在更复杂的情况下避免或控制此问题?

【问题讨论】:

标签: c++ floating-point floating-accuracy associativity


【解决方案1】:

浮点加法和乘法是可交换的,但不是结合的或分配的。因此,正如您所观察到的,cents_v1cents_v2 实际上可能代表不同的值。

避免这些类型的浮点陷阱的一种通用技术是使用任意精度的算术库。有quite a few of these,我对它们不够熟悉,无法推荐一个。当然,使用任意精度的数字会导致性能损失,但在您知道算术是瓶颈之前,进一步优化是不明智的。

如果您绝对必须使用浮点运算,有多种经验法则可以提高长浮点计算的准确性;查看一些数值方法文本以获取更多信息。还有大量关于浮点计算准确性的研究,产生了一些rather cool software

【讨论】:

    【解决方案2】:

    由于四舍五入的问题,您永远不应该使用floatdouble 来表示货币单位(它们并不精确,而当涉及到金钱时,人们希望精确)。

    尝试将以下行添加到您的代码中:

    std::cout << std:: setprecision(17) << price << "\n";
    

    根据您的输入,结果是:

    31.129999999999999
    

    所以你要么需要创建一个类来代表你的钱(这不是一个坏主意)。或者使用整数(因为整数总是精确的)。因此,将金额存储为 美分 的数量,而不是欧元的数量。然后在显示时只转换为欧元和美分。

    class DecimalCurrencyUnit
    {
        long   cents;
        public:
            DecimalCurrencyUnit()
                : cents(0)
            {}
            DecimalCurrencyUnit(long euros, long cent)
                : cents(euros * 100 + cent)
            {}
            friend std::ostream& operator<<(std::ostream& s, DecimalCurrencyUnit const& out)
            {
                return s << '€'
                         << (out.cents / 100) << "." 
                         << std::setw(2) << std::setfill('0') << (out.cents % 100);
            }
            friend std::istream& operator>>(std::istream& s, DecimalCurrencyUnit& in)
            {
                long euros;
                long cent;
                char s;
                char x;
                if ((s >> s >> euros >> x >> cent) && s == '€' && x == '.' && cent < 100)
                {
                    in.cents = euros * 100 + cent;
                }
                else
                {
                    s.setsetate(std::ios::bad);
                }
                return s;
            }
            // Add standard operators here for *+/- etc.
    }
    

    【讨论】:

    • 也许long long,确保连我国的国债都可以代表...
    • @Bob__:如果您想在生产中执行此操作,那么任意精度库可能是一个更好的主意。如果你只是在胡闹long long
    猜你喜欢
    • 2023-01-18
    • 2018-05-17
    • 2012-05-07
    • 2022-08-08
    • 1970-01-01
    • 2021-04-30
    • 1970-01-01
    • 2018-06-25
    • 1970-01-01
    相关资源
    最近更新 更多