0

Есть код:

#include <iostream>
#include <iomanip>
using namespace std;

int main() { float comission = 0, minsumm = 0; cin >> comission >> minsumm; comission = comission / 100; int num; cin >> num; float sum = 0; for (int i = 0; i < num; i++) { float temp; cin >> temp; sum += temp; double proc = temp * comission; if (proc >= minsumm) sum +=proc; else sum += minsumm; } cout << std::fixed << setprecision(2) << sum;

}

К нему у меня есть пара вопросов. Почему при comission = comission / 100 3 / 100 становится 0.0299999993? Почему введенное 1.20 в переменную minsumm с клавиатуры становится 1.20000005? Вернее, почему так я знаю, но не знаю как исправить это. Можно было просто обрезать все знаки после какого-нибудь четвертого знака, т. к. дальше расчетов в задаче точно не будет, но тогда 3 / 100 станет 0.0299 вместо 0.0300. А округление в задаче всегда должно быть (все после второго знака после запятой число должно округляться вниз), но если округлять тот же пример то comission будет 0.02, а не 0.03. Еще я читал про decimal, но в c++ в стандартных библиотеках его нет, нужно что-то качать, а в задаче разрешено использовать только стандартные библиотеки.

l0cus
  • 11
  • Вообще-то при выводе все нормально округляется (https://ideone.com/H2t4Wt). В чем тут проблема? – Harry Nov 09 '22 at 18:12
  • В бухгалтерии этот вопрос уже решён. Всё считается в копейках. В компьютерах в дробных числах сразу появляются маленькие ошибки, а при вычислении целых чисел - нет. – AlexGlebe Nov 09 '22 at 18:19
  • но формат на выводе обязательно должен быть с двумя знаками после запятой – l0cus Nov 09 '22 at 18:52
  • может вам на bcd код перейти? – Pavel Gridin Nov 09 '22 at 20:37

1 Answers1

1

Если брать вопрос почему, то чтобы сэкономить Ваше время предлагаю ознакомиться с этим видео. Если брать вопрос как починить, то для этого уже существует решение. Это эпсилоны. Они являются что-то вроде погрешности при вычислениях типов с плавающей точкой. К счастью, стандартная библиотека C++ уже имеет такие константы. К примеру, для типа float имеется константа FLT_EPSILON. Соответственно всё что нужно вам перед округлением - это добавлять константу к числу.

#include <iostream>
#include <iomanip>
#include <float.h>

int main() { float num{0.2999999999f}; std::cout << std::fixed << std::setprecision(2) << num + FLT_EPSILON << std::endl;

return 0;

}

Помимо этого думаю следует добавить что это также может зависеть и от компилятора. Лично по моему опыту при работе с GCC такой проблемы не было, но при работе с LLVM фронтендом, возникали те же проблемы. Дело в том что подобные модификации может вносить, либо программист, либо компилятор. Но лучшая практика - это писать код, который будет работать на всех компиляторах и системах.

  • Когда я считаю comission, в одном из примеров оно 100, и чтобы считать комиссию от чисел нужно разделить его на 100, чтобы потом умножать и получать проценты. В случае с comission = 3 после деления оно становится 0.2(9), а после добавления FLT_EPSILON становится еще более страшным 0.030000119209289550... – l0cus Nov 09 '22 at 19:38
  • Я вам написал написал пример в случае с 0.029. В случае 100, вам нужно сравнивать значение с эпсилоном. Число 0.030000119209289550 это 0.03 с округлением до 2-х цифр. Я подумал это то что Вы хотели. Или я неправильно понял задачу? – Wusiki Jeronii Nov 09 '22 at 19:46
  • Я еще немного почитал, получается что в double и float вообще никак не получится хранить 0.3 ровно? Суть задачи в том, чтобы ровно подсчитать все комиссии. Задача проверяется с помощью тестов, и при comission = 0.0299999999999999 не проходит около 6 тестов из 51, а при этом значении около 35. Значит, лишние знаки после запятой будут препятствовать правильным ответам на остальных тестах. А есди округлить все до 3х знаков, число снова превращается в 0.0299999999999. К сожалению, у меня нет всех тестов, и я не могу проверить, где конкретно ломается программаю – l0cus Nov 09 '22 at 19:56
  • Да, всё верно. Здесь как и везде. Всегда есть погрешность ;) – Wusiki Jeronii Nov 09 '22 at 19:58
  • И нет никакого типа, в котором памяти выделялось достаточно, чтобы хранить 4 знака после запятой совсем без погрешности? float и double могут держать огромные числа, в задачах входные данные редко превышают 5 знаков после запятой – l0cus Nov 09 '22 at 20:00
  • Такой принцип работы у чисел с плавающей запятой. В видео помомне объяснялось, что в результате получается бесконечные 0 и 1-цы. Максимум что можно сделать - это увеличить точность. К примеру, double double Я честно говоря не помню (да и никогда не использовал) есть ли в c++ тип real, но в других языках программирования он есть. Это тип зависимый от процессора. Минимум он double. Максимум что может позволить процессор. Но это я так, к слову. В данном случае это никак не повлияет на результат. В смысле подобная точность ничего не поменяет. – Wusiki Jeronii Nov 09 '22 at 20:07