Немного разные ответы при изменении ассоциативности

С помощью следующего простого упражнения 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

Решение

Из-за проблем округления вы никогда не должны использовать 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

Другие решения

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

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

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

3

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector