2016-05-02 8 views
2

С следующей простой 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. 

Как бороться с этим проблема? Есть ли правило в программировании, чтобы избежать или контролировать эту проблему в более сложных ситуациях?

+2

Стандартный ответ на этот вопрос [Что должен знать каждый компьютерный ученый о плавающей запятой] (https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html) –

+0

Преобразование результатов FP в целые числа могут сильно пострадать из-за усечения. Предложите округление. 'cents_v1 = round (цена * 100 - евро * 100);' – chux

ответ

1

Из-за проблем округления вы не должны использовать float или double для представления денежных единиц (они не точны и когда дело доходит до денег, люди хотят быть точными).

Попробуйте добавить следующую строку кода:

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. 
} 
+1

Возможно, долгое время, чтобы быть уверенным, что даже государственный долг моей страны может быть представлен ... –

+0

@Bob__: Если вы хотите сделать это в производстве, библиотека произвольной точности может быть лучшей идеей. Если вы просто возитесь с 'long long' –

3

Сложение и умножение с плавающей запятой являются коммутативными, но не ассоциативными или дистрибутивными. Таким образом, cents_v1 и cents_v2 могут фактически представлять разные значения, как вы заметили.

Общей методикой, позволяющей избежать этих типов ошибок с плавающей запятой, является использование арифметической библиотеки произвольной точности. Есть quite a few of these, и я недостаточно знаком с ними, чтобы рекомендовать один за другим. Разумеется, использование чисел произвольной точности приводит к снижению производительности, но пока вы не узнаете, что ваша арифметика является узким местом, нецелесообразно оптимизировать ее дальше.

Если вы абсолютно должны использовать арифметику с плавающей запятой, для повышения точности при длинных вычислениях с плавающей запятой существует множество правил большого пальца; посмотрите на некоторые тексты числовых методов для получения дополнительной информации. Существует также хороший объем исследований по точности расчета с плавающей запятой, который произвел около rather cool software.