
#ifndef __FIXED_H_
#define __FIXED_H_

#define __STDC_FORMAT_MACROS

#include <cctype>
#include <cmath>
#include <cinttypes>
#include <cstdio>
#include <cstdint>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <limits>
#include <sstream>
#include <stdexcept>
#include <type_traits>
#include <map>


#if __GNUC__ >= 3
#define __unlikely(cond) __builtin_expect((cond), 0)
#define __likely(cond)   __builtin_expect((cond), 1)
#else
#define __unlikely(cond) (cond)
#define __likely(cond)   (cond)
#endif

// Defaults for compile time options
#ifndef BEHAVIOR_PARSE_TRUNCATE
#define BEHAVIOR_PARSE_TRUNCATE     0   // Default: disable
#endif

#ifndef SUPPORT_PARSE_EXPONENT
#define SUPPORT_PARSE_EXPONENT      1   // Default: enable
#endif

template<typename T>
constexpr T calculateMax(size_t decimal_digits)
{
    T val = 0;
    for (uint32_t idx = 0; idx < decimal_digits; ++idx)
    {
        val *= 10;
        val += 9;
    }
    return val;
}


template<typename IntegerType, typename FractionalType, 
         typename StrToIntegerTypeFunc,
         typename StrToFracTypeFunc>
class fixed 
{
    static_assert(std::is_integral<IntegerType>::value,
                  "IntegerType must be an integral type");
    static_assert(std::is_integral<FractionalType>::value,
                  "FractionalType must be an integral type");

    static_assert(std::is_signed<IntegerType>::value,
                  "IntegerType must be a signed type");
    static_assert(std::is_unsigned<FractionalType>::value,
                  "FractionalType must be an unsigned type");

public:

    static constexpr size_t integer_bits = sizeof(IntegerType) * 8;
    static constexpr size_t fractional_bits = sizeof(FractionalType) * 8;

    static constexpr size_t integer_decimal_digits =
        std::floor(std::log10(std::numeric_limits<IntegerType>::max()));
    static constexpr size_t fractional_decimal_digits =
        std::floor(std::log10(std::numeric_limits<FractionalType>::max()));
    static constexpr size_t total_decimal_digits = integer_decimal_digits + 
                                                   fractional_decimal_digits;

    static constexpr IntegerType MAX_INTEGER_VALUE = 
        (calculateMax<IntegerType>(integer_decimal_digits));
    static constexpr IntegerType MIN_INTEGER_VALUE = 0;

    static constexpr FractionalType MAX_FRACTIONAL_VALUE = 
        (calculateMax<FractionalType>(fractional_decimal_digits));
    static constexpr FractionalType MIN_FRACTIONAL_VALUE = 0;

    static constexpr IntegerType NEGATIVE_ZERO = MAX_INTEGER_VALUE + 2;

    static const uint64_t SCALE_VALUES[20];
    static const std::map<uint64_t, uint32_t> DIGIT_LOOKUP_TABLE;

    // Constructors
    explicit fixed()
        : m_integer(0),
          m_fractional(0)
    {}

    explicit fixed(IntegerType integerVal)
        : m_integer(__checkIntOverflow(integerVal)),
          m_fractional(0)
    {}

    explicit fixed(IntegerType integerVal, FractionalType fractionalVal)
        : m_integer(__checkIntOverflow(integerVal)),
          m_fractional(__checkFracOverflow(fractionalVal))
    {
        // Scale the fractional value appropriately
        m_fractional = __checkFracOverflow(m_fractional * 
                                           getFracScaleValue(m_fractional));
    }
    
    explicit fixed(IntegerType integerVal, FractionalType fractionalVal, uint32_t leadingZeros)
        : m_integer(__checkIntOverflow(integerVal)),
          m_fractional(__checkFracOverflow(fractionalVal))
    {
        // Scale the fractional value appropriately
        m_fractional = __checkFracOverflow(m_fractional * 
                                           getFracScaleValue(m_fractional,
                                                             leadingZeros));
    }

    // Default copy constructor, and assignment operator
    fixed(const fixed&) = default;
    fixed& operator=(const fixed&) = default;

    // Reassign value to type with a prescaled fractional value.
    void assign(IntegerType integerVal, FractionalType fractionalVal)
    {
        m_integer = __checkIntOverflow(integerVal);
        
        // Fractional value is prescaled to fractional type precision
        m_fractional = __checkFracOverflow(fractionalVal);
    }

    // Assign a new value with an *unscaled* fractional part (NOTE: use the 
    // "leadingZeros" parameter to represent the number of leading zeros in the
    // unscaled fractional part.
    void assignUnscaledFrac(
        IntegerType integerVal, 
        FractionalType fractionalVal,
        uint32_t leadingZeros=0)
    {
        m_integer = __checkIntOverflow(integerVal);
        
        // Scale the fractional value appropriately
        m_fractional = __checkFracOverflow(fractionalVal * 
                                           getFracScaleValue(fractionalVal, 
                                                             leadingZeros));
    }

    // Parse value from string input
    uint32_t parse(const char* input)
    {
        char* endPtr = nullptr;
        m_integer = __checkIntOverflow(strto_inttype(input, &endPtr, 10));
        m_fractional = 0;
        bool negativeZeroFlag = false;

        if (std::isdigit(*endPtr))
        {
            throw std::out_of_range("Integer value is out of range.");
        }
        
        // Check for negative zero corner case with leading zero digit
        if (__unlikely(m_integer == 0 && endPtr - input >= 2 && *(endPtr - 2) == '-'))
        {
            negativeZeroFlag = true;
        }

        // If the ending char is a period we can now parse the fractional part
        uint32_t fracLen = 0;        
        if (*endPtr == '.'|| (endPtr[0] == '-' && endPtr[1] == '.'))
        {
            // Check for negative zero corner case without leading zero digit
            if (__unlikely(endPtr[0] == '-'))
            {
                ++endPtr;
                negativeZeroFlag = true;
            }
            
            char* fracEndPtr = nullptr;
            FractionalType fracTemp =
                __checkFracOverflow(strto_fractype(endPtr + 1, &fracEndPtr, 10));
                
            fracLen = (fracEndPtr - endPtr) - 1;
            if (__unlikely(fracLen > fractional_decimal_digits))
            {
#if BEHAVIOR_PARSE_TRUNCATE
                // Fractional length exceeds supported number of fractional
                // decimal digits, downscale value to only include supported 
                // number of digits
                uint32_t idx = fracLen - fractional_decimal_digits;
                m_fractional = fracTemp / SCALE_VALUES[idx];
#else
                throw std::out_of_range("Fractional length exceeds supported "
                                        "number of fractional decimal digits.");
#endif
            }
            else
            {
                uint32_t idx = fractional_decimal_digits - fracLen;
                m_fractional = __checkFracOverflow(fracTemp * SCALE_VALUES[idx]);
            }
            endPtr = fracEndPtr;
        }
        
#if SUPPORT_PARSE_EXPONENT
        // Attempt to parse exponent
        if (*endPtr == 'e' || *endPtr == 'E')
        {
            // Set negative zero flag here if integer part is negative then
            // clear below if final integer part after exponent adjustment is
            // not zero
            if (m_integer < 0)
            {
                negativeZeroFlag = true;
            }
            
            char* expEndPtr = nullptr;
            long exponent = strtol(++endPtr, &expEndPtr, 10);
            uint32_t integerLen = 0;
            if (m_integer != 0)
            {
                integerLen = DIGIT_LOOKUP_TABLE.upper_bound(std::abs(m_integer))->second;
            }
            uint32_t scaledFracLen = 
                DIGIT_LOOKUP_TABLE.upper_bound(m_fractional)->second;
            
            if (exponent >= 0)
            {
                // Detect potential overflow based on the exponent
                if ((m_integer != 0)  && 
                    (integerLen + static_cast<size_t>(exponent)) > 
                     integer_decimal_digits)
                {
                    throw std::out_of_range("Positive exponent exceeds maximum"
                                            " decimal digit range.");
                }
                else if (scaledFracLen + static_cast<size_t>(exponent)
                         > total_decimal_digits)
                {
                    throw std::out_of_range("Positive exponent exceeds maximum"
                                            " decimal digit range (2).");
                }
                
                long fracExponent = exponent;
                if (static_cast<size_t>(fracExponent) > fractional_decimal_digits)
                {
                    fracExponent = fractional_decimal_digits;
                }
                
                bool intZeroFlag = true;
                if (m_integer != 0)
                {
                    // Rescale integer value if it is greater than zero
                    intZeroFlag = false;
                    m_integer *= static_cast<IntegerType>(SCALE_VALUES[exponent]);
                }
                
                uint64_t scaler = SCALE_VALUES[fractional_decimal_digits - 
                                               fracExponent];
                IntegerType temp = m_fractional / scaler;
                m_integer += temp;
                m_fractional -= (temp * scaler);
                m_fractional *= SCALE_VALUES[fracExponent];
                
                if (intZeroFlag)
                {
                    // Rescale integer value if it was zero
                    m_integer *= SCALE_VALUES[exponent - fracExponent];
                }
            }
            else
            {
                // Handle negative exponent
                // Detect potential overflow based on the exponent
                if (m_fractional != 0 && 
                    (static_cast<size_t>(-exponent) > 
                     (fractional_decimal_digits - fracLen)))
                {
                    throw std::out_of_range("Negative exponent exceeds maximum"
                                            " decimal digit range.");
                }
                else if (static_cast<size_t>(-exponent) >= 
                         (integerLen + fractional_decimal_digits))
                {
                    throw std::out_of_range("Negative exponent exceeds maximum"
                                            " decimal digit range.");
                }

                FractionalType temp = 0;
                if (m_fractional != 0)
                {
                    // CASE 1: fractional non-zero
                    m_fractional /= SCALE_VALUES[-exponent];
                    temp = std::abs(m_integer) % SCALE_VALUES[-exponent];
                    temp *= SCALE_VALUES[fractional_decimal_digits + exponent];
                    m_integer /= static_cast<IntegerType>(SCALE_VALUES[-exponent]);
                    m_fractional += temp;
                }
                else
                {
                    // CASE 2: fractional zero
                    long intExponent = std::abs(exponent);
                    if (static_cast<size_t>(intExponent) > integer_decimal_digits)
                    {
                        intExponent = integer_decimal_digits;
                    }
                    
                    temp = std::abs(m_integer) % SCALE_VALUES[intExponent];
                    temp *= SCALE_VALUES[fractional_decimal_digits - intExponent];
                    m_integer /= SCALE_VALUES[intExponent];
                    m_fractional += temp;
                    
                    if (std::abs(exponent) > intExponent)
                    {
                        long fracExponent = std::abs(exponent) - intExponent;
                        m_fractional /= SCALE_VALUES[fracExponent];
                    }
                }
            }
        
            // Clear negative zero flag if final adjusted integer part is not
            // zero. Flag will remain set iff initial integer part was negative
            // and integer part after exponent adjustment is zero.
            if (__likely(m_integer != 0))
            {
                negativeZeroFlag = false;
            }
        }

#endif
        if (__unlikely(negativeZeroFlag))
        {
            m_integer = NEGATIVE_ZERO;
        }

        // Calculate and return overall length
        return (endPtr - input);
    }

    constexpr inline void absval()
    {
        if (__unlikely(m_integer == NEGATIVE_ZERO))
        {
            m_integer = 0;
        }
        else
        {
            m_integer = std::abs(m_integer);
        }
    }
    
    // This is modeled after std::signbit() for floating point types
    constexpr inline bool signbit() const
    {
        return (m_integer < 0 || m_integer == NEGATIVE_ZERO);
    }
    
    constexpr inline void negate()
    {
        if (__unlikely(m_integer == 0))
        {
            m_integer = NEGATIVE_ZERO;
        }
        else
        {
            m_integer *= -1;
        }
    }
    
    // Disallow conversion operators (for now)
    explicit operator int() = delete;
    explicit operator float() = delete;
    explicit operator double() = delete;

    constexpr inline fixed operator-() const noexcept = delete;
    constexpr inline fixed operator!() const noexcept = delete;
    constexpr inline fixed operator~() const noexcept = delete;
    inline fixed& operator+=(const fixed& y) noexcept = delete;
    inline fixed& operator-=(const fixed& y) noexcept = delete;
    inline fixed& operator*=(const fixed& y) noexcept = delete;
    inline fixed& operator/=(const fixed& y) noexcept = delete;

    // Comparison operators
    template<typename I, typename F, typename STI, typename STF>
    friend constexpr inline bool operator==(
        const fixed<I, F, STI, STF>& x, const fixed<I, F, STI, STF>& y) noexcept;

    template<typename I, typename F, typename STI, typename STF>
    friend constexpr inline bool operator!=(
        const fixed<I, F, STI, STF>& x, const fixed<I, F, STI, STF>& y) noexcept;

    template<typename I, typename F, typename STI, typename STF>
    friend constexpr inline bool operator<(
        const fixed<I, F, STI, STF>& x, const fixed<I, F, STI, STF>& y) noexcept;

    template<typename I, typename F, typename STI, typename STF>
    friend constexpr inline bool operator>(
        const fixed<I, F, STI, STF>& x, const fixed<I, F, STI, STF>& y) noexcept;

    template<typename I, typename F, typename STI, typename STF>
    friend constexpr inline bool operator<=(
        const fixed<I, F, STI, STF>& x, const fixed<I, F, STI, STF>& y) noexcept;

    template<typename I, typename F, typename STI, typename STF>
    friend constexpr inline bool operator>=(
        const fixed<I, F, STI, STF>& x, const fixed<I, F, STI, STF>& y) noexcept;

    // Divide by unsigned int
    template<typename I, typename F, typename STI, typename STF>
    friend constexpr inline fixed<I, F, STI, STF> operator/(
        const fixed<I, F, STI, STF>& x, const int64_t& y);

    // Output stream operator
    template<typename I, typename F, typename STI, typename STF>
    friend inline std::ostream& operator<<(
        std::ostream& os, const fixed<I, F, STI, STF>& rhs);


private:

    static StrToIntegerTypeFunc strto_inttype;
    static StrToFracTypeFunc strto_fractype;

    static inline IntegerType __checkIntOverflow(IntegerType integerVal)
    {
        if (__unlikely(integerVal > MAX_INTEGER_VALUE))
        {
            std::ostringstream msg;
            msg << "Integer value: " << integerVal << " exceeds maximum "
                << "integer range of type (" << MAX_INTEGER_VALUE << ")!";
            throw std::out_of_range(msg.str());
        }
        return integerVal;
    }

    static inline FractionalType __checkFracOverflow(FractionalType fractionalVal)
    {
        if (__unlikely(fractionalVal > MAX_FRACTIONAL_VALUE))
        {
            std::ostringstream msg;
            msg << "Fractional value: " << fractionalVal << " exceeds maximum "
                << "fractional range of type (" << MAX_FRACTIONAL_VALUE << ")!";
            throw std::out_of_range(msg.str());
        }
        return fractionalVal;
    }
    
    // Helper function to get the approprate scale value for an unscaled input
    // value of the fractional type
    static inline FractionalType getFracScaleValue(
        FractionalType value,
        uint32_t leadingZeros=0)
    {
        uint32_t index = fractional_decimal_digits - 
                         (DIGIT_LOOKUP_TABLE.upper_bound(value)->second + 
                          leadingZeros);
        return SCALE_VALUES[index];
    }

    IntegerType m_integer;
    FractionalType m_fractional;
};

// Functor for calling std::strtoll()
struct strtoll_ftor {
    inline int64_t operator()(const char* str, char** str_end, int base) 
    { 
        return std::strtoll(str, str_end, base); 
    }
};

// Functor for calling std::strtoull()
struct strtoull_ftor {
    inline uint64_t operator()(const char* str, char** str_end, int base) 
    { 
        return std::strtoull(str, str_end, base); 
    }
};

// Predefined types
using fixed_8_8  = fixed<int8_t, uint8_t, strtoll_ftor, strtoull_ftor>;
using fixed_8_16 = fixed<int8_t, uint16_t, strtoll_ftor, strtoull_ftor>;
using fixed_8_32 = fixed<int8_t, uint32_t, strtoll_ftor, strtoull_ftor>;
using fixed_8_64 = fixed<int8_t, uint64_t, strtoll_ftor, strtoull_ftor>;

using fixed_16_8  = fixed<int16_t, uint8_t, strtoll_ftor, strtoull_ftor>;
using fixed_16_16 = fixed<int16_t, uint16_t, strtoll_ftor, strtoull_ftor>;
using fixed_16_32 = fixed<int16_t, uint32_t, strtoll_ftor, strtoull_ftor>;
using fixed_16_64 = fixed<int16_t, uint64_t, strtoll_ftor, strtoull_ftor>;

using fixed_32_8  = fixed<int32_t, uint8_t, strtoll_ftor, strtoull_ftor>;
using fixed_32_16 = fixed<int32_t, uint16_t, strtoll_ftor, strtoull_ftor>;
using fixed_32_32 = fixed<int32_t, uint32_t, strtoll_ftor, strtoull_ftor>;
using fixed_32_64 = fixed<int32_t, uint64_t, strtoll_ftor, strtoull_ftor>;

using fixed_64_8  = fixed<int64_t, uint8_t, strtoll_ftor, strtoull_ftor>;
using fixed_64_16 = fixed<int64_t, uint16_t, strtoll_ftor, strtoull_ftor>;
using fixed_64_32 = fixed<int64_t, uint32_t, strtoll_ftor, strtoull_ftor>;
using fixed_64_64 = fixed<int64_t, uint64_t, strtoll_ftor, strtoull_ftor>;

// Precomputed scale value constants
template<typename I, typename F, typename STI, typename STF>
const uint64_t fixed<I, F, STI, STF>::SCALE_VALUES[20] = 
{
    /*  0 */ 1ULL,
    /*  1 */ 10ULL,
    /*  2 */ 100ULL,
    /*  3 */ 1000ULL,
    /*  4 */ 10000ULL,
    /*  5 */ 100000ULL,
    /*  6 */ 1000000ULL,
    /*  7 */ 10000000ULL,
    /*  8 */ 100000000ULL,
    /*  9 */ 1000000000ULL,
    /* 10 */ 10000000000ULL,
    /* 11 */ 100000000000ULL,
    /* 12 */ 1000000000000ULL,
    /* 13 */ 10000000000000ULL,
    /* 14 */ 100000000000000ULL,
    /* 15 */ 1000000000000000ULL,
    /* 16 */ 10000000000000000ULL,
    /* 17 */ 100000000000000000ULL,
    /* 18 */ 1000000000000000000ULL,
    /* 19 */ 10000000000000000000ULL
};

template<typename I, typename F, typename STI, typename STF>
const std::map<uint64_t, uint32_t> fixed<I, F, STI, STF>::DIGIT_LOOKUP_TABLE{
    {1ULL, 0},
    {10ULL, 1},
    {100ULL, 2},
    {1000ULL, 3},
    {10000ULL, 4},
    {100000ULL, 5},
    {1000000ULL, 6},
    {10000000ULL, 7},
    {100000000ULL, 8},
    {1000000000ULL, 9},
    {10000000000ULL, 10},
    {100000000000ULL, 11},
    {1000000000000ULL, 12},
    {10000000000000ULL, 13},
    {100000000000000ULL, 14},
    {1000000000000000ULL, 15},
    {10000000000000000ULL, 16},
    {100000000000000000ULL, 17},
    {1000000000000000000ULL, 18},
    {std::numeric_limits<uint64_t>::max(), 19}  // Special case for uint64_t max value
};

template<typename I, typename F, typename STI, typename STF>
constexpr inline bool operator==(
    const fixed<I, F, STI, STF>& x, const fixed<I, F, STI, STF>& y) noexcept
{
    return (x.m_integer == y.m_integer && x.m_fractional == y.m_fractional);
}

template<typename I, typename F, typename STI, typename STF>
constexpr inline bool operator!=(
    const fixed<I, F, STI, STF>& x, const fixed<I, F, STI, STF>& y) noexcept
{
    return (! (x == y));
}

template<typename I, typename F, typename STI, typename STF>
constexpr inline bool operator<(
    const fixed<I, F, STI, STF>& x, const fixed<I, F, STI, STF>& y) noexcept
{
    if (x.signbit() && (! y.signbit()))
    {
        return true;
    }
    else if ((! x.signbit()) && y.signbit())
    {
        return false;
    }
    else
    {
        I integerX = x.m_integer;
        if (__unlikely((x.m_integer == fixed<I, F, STI, STF>::NEGATIVE_ZERO)))
        {
            integerX = 0;
        }
        
        I integerY = y.m_integer;
        if (__unlikely((y.m_integer == fixed<I, F, STI, STF>::NEGATIVE_ZERO)))
        {
            integerY = 0;
        }
        
        if (integerX == integerY)
        {
            return (x.m_fractional < y.m_fractional);
        }
        else
        {
            return (integerX < integerY);
        }
    }
    
    return false;
}

template<typename I, typename F, typename STI, typename STF>
constexpr inline bool operator>(
    const fixed<I, F, STI, STF>& x, const fixed<I, F, STI, STF>& y) noexcept
{
    if (x.signbit() && (! y.signbit()))
    {
        return false;
    }
    else if ((! x.signbit()) && y.signbit())
    {
        return true;
    }
    else
    {
        I integerX = x.m_integer;
        if (__unlikely((x.m_integer == fixed<I, F, STI, STF>::NEGATIVE_ZERO)))
        {
            integerX = 0;
        }
        
        I integerY = y.m_integer;
        if (__unlikely((y.m_integer == fixed<I, F, STI, STF>::NEGATIVE_ZERO)))
        {
            integerY = 0;
        }
        
        if (integerX == integerY)
        {
            return (x.m_fractional > y.m_fractional);
        }
        else
        {
            return (integerX > integerY);
        }
    }
    
    return false;
}

template<typename I, typename F, typename STI, typename STF>
constexpr inline bool operator<=(
    const fixed<I, F, STI, STF>& x, const fixed<I, F, STI, STF>& y) noexcept
{
    return (x == y || x < y);
}

template<typename I, typename F, typename STI, typename STF>
constexpr inline bool operator>=(
    const fixed<I, F, STI, STF>& x, const fixed<I, F, STI, STF>& y) noexcept
{
    return (x == y || x > y);
}

// Addition -- not yet supported
template<typename I, typename F, typename STI, typename STF>
constexpr inline fixed<I, F, STI, STF> operator+(
    const fixed<I, F, STI, STF>& x, const fixed<I, F, STI, STF>& y) noexcept = delete;

// Subtraction -- not yet supported
template<typename I, typename F, typename STI, typename STF>
constexpr inline fixed<I, F, STI, STF> operator-(
    const fixed<I, F, STI, STF>& x, const fixed<I, F, STI, STF>& y) noexcept = delete;

// Multiplication -- not yet supported
template<typename I, typename F, typename STI, typename STF>
constexpr inline fixed<I, F, STI, STF> operator*(
    const fixed<I, F, STI, STF>& x, const fixed<I, F, STI, STF>& y) noexcept = delete;

// Division -- not yet supported
template<typename I, typename F, typename STI, typename STF>
constexpr inline fixed<I, F, STI, STF> operator/(
    const fixed<I, F, STI, STF>& x, const fixed<I, F, STI, STF>& y) noexcept = delete;

// Division by unsigned int
template<typename I, typename F, typename STI, typename STF>
constexpr inline fixed<I, F, STI, STF> operator/(
     const fixed<I, F, STI, STF>& x, const int64_t& y)
{
    fixed<I, F, STI, STF> newVal;
    bool negativeDivisorFlag = (y < 0);
    bool negativeDividendFlag = (x.m_integer < 0) || 
                                (x.m_integer == fixed<I, F, STI, STF>::NEGATIVE_ZERO);
    bool negativeZeroFlag = false;
    I integerX = x.m_integer;
    if (__unlikely((integerX == fixed<I, F, STI, STF>::NEGATIVE_ZERO)))
    {
        integerX = 0;
        negativeZeroFlag = true;
    }
    
    newVal.m_integer = integerX / y;
    newVal.m_fractional = std::abs(integerX) % y;
    newVal.m_fractional *= x.getFracScaleValue(newVal.m_fractional);
    newVal.m_fractional += x.m_fractional / std::abs(y);
    
    // Ensure quotient has correct sign value
    if (newVal.m_integer == 0 && negativeDividendFlag && negativeDivisorFlag)
    {
        newVal.m_integer = 0;
    }
    else if ((newVal.m_integer == 0 && (negativeDividendFlag || negativeDivisorFlag))
              || __unlikely(negativeZeroFlag))
    {
        newVal.m_integer = fixed<I, F, STI, STF>::NEGATIVE_ZERO;
    }
    
    return newVal;
}

// Stream output
template<typename I, typename F, typename STI, typename STF>
inline std::ostream& operator<<(std::ostream& os, const fixed<I, F, STI, STF>& rhs)
{
#if 0
    // Print full fractional precision always (even with trailing zeros)
    return os << std::fixed << rhs.m_integer << "."
              << std::setw(fixed<I, F, STI, STF>::fractional_decimal_digits)
              << std::setfill('0') << rhs.m_fractional;
#else
    // Print fractional part without trailing zeros
    char buffer[fixed<I, F, STI, STF>::fractional_decimal_digits + 1]{};
    std::snprintf(buffer, fixed<I, F, STI, STF>::fractional_decimal_digits + 1,
                  "%" PRIu64, rhs.m_fractional);
    
    // NOTE: if fractional part is zero always print at least one zero
    int idx = fixed<I, F, STI, STF>::fractional_decimal_digits;
    bool found = false;
    for (; idx >= 0; --idx)
    {
        if (std::isdigit(buffer[idx]) && buffer[idx] != '0')
        {
            found = true;
            break;
        }
    }
    if (found || idx == -1)
    {
        buffer[idx + 1] = '\0';
    }
    
    uint32_t leadingZeros = (rhs.m_fractional == 0) ? 1 : 
        fixed<I, F, STI, STF>::fractional_decimal_digits - 
        fixed<I, F, STI, STF>::DIGIT_LOOKUP_TABLE.upper_bound(rhs.m_fractional)->second;
        
    // Handle special case of negative zero
    os << std::fixed;
    if (__unlikely((rhs.m_integer == fixed<I, F, STI, STF>::NEGATIVE_ZERO)))
    {
        os << "-0.";
    }
    else
    {
        os << rhs.m_integer << ".";
    }
    
    // Output any fractional leading zeros
    for (uint32_t idx = 0; idx < leadingZeros; ++idx)
    {
        os << "0";
    }
    
    return os << buffer;
#endif
}

#endif  // FIXED_H_
