I am creating unit tests for a function that rounds "rational" numbers stored as strings. The current rounding implementation casts the strings to a floating point type:
#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();
}
In one of my tests, I input the number 3.55, which is represented as 3.5499999... on my machine. It all goes well when rounding from 2 decimals to 10. However, when I round to the first decimal, I unsurprisingly get 3.5 instead of 3.6.
What would be a simple method to avoid this error?
Currently, the best solution I was able to find was to use a multiple precision type:
#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();
}
While this solution addresses the problem in a straightforward way (vs manually parsing strings or creating a Rational number class), I find it overkill for such a simple problem.
To find ways to address this problem, I peeked at some calculators' implementations. I looked at gnome-calculator's source code and found that it uses GNU MPFR. I then looked at SpeedCrunch's implementation and found it re-uses the same code as bc, which employs a rational type (numerator, denominator).
Am I overlooking something?