Search code examples
c++stringmathc++17radix

How can I stringify a fraction with N decimals in C++


I want to stringify a fraction of unsigned integers in C++ with variable precision. So 1/3 would be printed as 0.33 using a precision of 2. I know that float and std::ostream::precision could be used for a quick and dirty solution:

std::string stringifyFraction(unsigned numerator,
                              unsigned denominator,
                              unsigned precision)
{
    std::stringstream output;
    output.precision(precision);
    output << static_cast<float>(numerator) / denominator;
    return output.str();
}

However, this is not good enough because float has limited precision and can't actually represent decimal numbers accurately. What other options do I have? Even a double would fail if I wanted 100 digits or so, or in case of a recurring fraction.


Solution

  • It's always possible to just perform long division to stringify digit-by-digit. Note that the result consists of an integral part and a fractional part. We can get the integral part by simply dividing using the / operator and calling std::to_string. For the rest, we need the following function:

    #include <string>
    
    std::string stringifyFraction(const unsigned num,
                                  const unsigned den,
                                  const unsigned precision)
    {
        constexpr unsigned base = 10;
    
        // prevent division by zero if necessary
        if (den == 0) {
            return "inf";
        }
    
        // integral part can be computed using regular division
        std::string result = std::to_string(num / den);
        
        // perform first step of long division
        // also cancel early if there is no fractional part
        unsigned tmp = num % den;
        if (tmp == 0 || precision == 0) {
            return result;
        }
    
        // reserve characters to avoid unnecessary re-allocation
        result.reserve(result.size() + precision + 1);
    
        // fractional part can be computed using long divison
        result += '.';
        for (size_t i = 0; i < precision; ++i) {
            tmp *= base;
            char nextDigit = '0' + static_cast<char>(tmp / den);
            result.push_back(nextDigit);
            tmp %= den;
        }
    
        return result;
    }
    

    You could easily extend this to work with other bases as well, by just making base a template parameter, but then you couldn't just use std::to_string anymore.