Я создаю модульные тесты для функции, которая округляет «рациональные» числа, хранящиеся в виде строк. Текущая реализация округления приводит строки к типу с плавающей запятой:
#include <boost/lexical_cast.hpp>
#include <iomanip>
#include <limits>
#include <sstream>
template<typename T = double,
size_t PRECISION = std::numeric_limits<T>::digits10>
std::string Round(const std::string& number)
{
std::stringstream ss{};
ss << std::fixed << std::setprecision(PRECISION);
ss << boost::lexical_cast<T>(number);
return ss.str();
}
В одном из моих тестов я ввел число 3.55, которое представлено как 3.5499999 … на моей машине. Все идет хорошо при округлении с 2 десятичных знаков до 10. Однако, когда я округляю до первого десятичного знака, я неудивительно, что получаю 3,5 вместо 3,6.
Какой простой способ избежать этой ошибки?
В настоящее время лучшее решение, которое мне удалось найти, — это использовать тип с множественной точностью:
#include <boost/multiprecision/cpp_dec_float.hpp>
#include <iomanip>
#include <sstream>
template<size_t PRECISION = 10>
std::string Round(const std::string& number)
{
using FixedPrecision =
boost::multiprecision::number<
boost::multiprecision::cpp_dec_float<PRECISION>>;
std::stringstream ss{};
ss << std::fixed << std::setprecision(PRECISION);
ss << FixedPrecision{number};
return ss.str();
}
Хотя это решение решает проблему простым способом (по сравнению с ручным синтаксическим анализом строк или созданием класса чисел Rational), я считаю его излишним для такой простой проблемы.
Чтобы найти способы решения этой проблемы, я заглянул в некоторые реализации калькуляторов. Я посмотрел на исходный код gnome-calculator и обнаружил, что он использует GNU MPFR. Затем я посмотрел на реализацию SpeedCrunch и обнаружил, что он повторно использует тот же код, что и bc, который использует рациональный тип (числитель, знаменатель).
Я что-то пропускаю?
Задача ещё не решена.
Других решений пока нет …