rational_as_string.hpp

Back to Rational numbers

pastel/sys/rational/

#ifndef PASTELSYS_RATIONAL_AS_STRING_HPP
#define PASTELSYS_RATIONAL_AS_STRING_HPP

#include "pastel/sys/rational/rational.h"
#include "pastel/sys/string/digit.h"

namespace Pastel
{

    template <typename Integer>
    std::string Rational<Integer>::asStringRatio() const
    {
        using Pastel::asString;

        std::string text = asString(m());
        if (n() != 1)
        {
            text += "/" + asString(n());
        }
        return text;
    }

    template <typename Integer>
    template <typename... ArgumentSet>
    std::string Rational<Integer>::asString(
        ArgumentSet&&... argumentSet) const
    {
        using Pastel::asString;

        integer base = PASTEL_ARG_S(base, 10);
        integer digits = PASTEL_ARG_S(digits, 3);
        bool showBase = PASTEL_ARG_S(showBase, false);
        bool shortenExact = PASTEL_ARG_S(shortenExact, true);
        Rounding rounding = PASTEL_ARG_S(rounding, Rounding::RoundUp);

        ENSURE_OP(base, >=, 2);
        ENSURE_OP(digits, >=, 0);

        // Handle the degenerate cases.
        switch(classify())
        {
            case NumberType::Infinity:
                return "inf";
            case NumberType::MinusInfinity:
                return "-inf";
            case NumberType::Nan:
                return "nan";
            default:
                // Fall-through
                ;
        };

        // This is where the result is constructed.
        std::string text;
        if (isNegative())
        {
            // Print the minus-sign.
            text += "-";
        }        

        // Reduce to a non-negative number.
        Integer m = abs(m_);

        // Compute the integer-part.
        Integer wholes = m / n();

        // Compute the fractional part.
        m -= wholes * n();

        // Print the integer-part.
        text += asString(wholes);

        if ((!zero(m) || !shortenExact) && digits > 0)
        {
            // The number has a fractional part.

            // Print the fractional point.
            text += '.';

            // Print the fractional part.
            for (integer i = 0; i < digits && (!zero(m) || !shortenExact); ++i)
            {
                m *= base;

                integer d = (integer)(m / n());
                m -= d * n();

                text += integerAsDigit(d);
            }
        }

        // What we have now is a truncated version
        // of the number.
        if (!zero(m) && rounding == Rounding::RoundUp)
        {
            // Find out one digit more for rounding.
            m *= base;

            integer lastDigit = (integer)(m / n());
            if (lastDigit >= base / 2)
            {
                // Round up.
                bool carry = true;
                for (integer i = text.size() - 1;i >= 0 && carry;--i)
                {
                    if (text[i] == '.')
                    {
                        // Skip the fractional point.
                        continue;
                    }

                    integer digit = digitAsInteger(text[i]);
                    ++digit;
                    carry = (digit == base);
                    if (carry)
                    {
                        digit = 0;
                    }

                    text[i] = integerAsDigit(digit);
                }

                if (carry)
                {
                    text = integerAsDigit(1) + text;
                }
            }
        }

        if (showBase)
        {
            // Print the base.
            text += '_' + asString(base);
        }

        return text;
    }

}

#endif