Program Listing for File BigDecimal.hpp

Return to documentation for file (src/rdf4cpp/BigDecimal.hpp)

#ifndef RDF4CPP_BIGDECIMAL_H
#define RDF4CPP_BIGDECIMAL_H

#include <functional>
#include <sstream>
#include <string>
#include <string_view>

#include <boost/multiprecision/cpp_int.hpp>
#include <dice/hash.hpp>

#include <rdf4cpp/Expected.hpp>
#include <rdf4cpp/InvalidNode.hpp>
#include <rdf4cpp/Assert.hpp>

namespace rdf4cpp {
enum struct RoundingMode {
    Floor,
    Ceil,
    Round,
    Trunc,
};

enum struct DecimalError {
    Overflow,
    NotDefined,  // aka NotANumber
};

template<typename T>
concept BigDecimalBaseType = std::numeric_limits<T>::is_specialized && !std::floating_point<T>;

template<BigDecimalBaseType UnscaledValue_t = boost::multiprecision::cpp_int, BigDecimalBaseType Exponent_t = uint32_t>
    requires(!std::signed_integral<Exponent_t> && !std::unsigned_integral<UnscaledValue_t>)
struct BigDecimal {
    // the entire class is loosely based on OpenJDKs BigDecimal: https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/master/src/java.base/share/classes/java/math/BigDecimal.java

private:
    UnscaledValue_t unscaled_value;
    Exponent_t exponent;

    static constexpr uint32_t base = 10;

    enum struct OverflowMode {
        Checked,
        UndefinedBehavior,
    };

    template<OverflowMode m, typename T>
    static constexpr bool add_checked(const T &a, const T &b, T &result) noexcept {
        if constexpr (std::is_integral_v<T> && m == OverflowMode::Checked) {
            return __builtin_add_overflow(a, b, &result);
        } else {
            result = a + b;
            return false;
        }
    }

    template<OverflowMode m, typename T>
    static constexpr bool sub_checked(const T &a, const T &b, T &result) noexcept {
        if constexpr (std::is_integral_v<T> && m == OverflowMode::Checked) {
            return __builtin_sub_overflow(a, b, &result);
        } else {
            result = a - b;
            return false;
        }
    }

    template<OverflowMode m, typename T>
    static constexpr bool mul_checked(const T &a, const T &b, T &result) noexcept {
        if constexpr (std::is_integral_v<T> && m == OverflowMode::Checked) {
            return __builtin_mul_overflow(a, b, &result);
        } else {
            result = a * b;
            return false;
        }
    }

    template<OverflowMode m, typename T>
    static constexpr bool pow_checked(const T &a, unsigned int b, T &result) noexcept {
        if constexpr (std::is_integral_v<T>) {
            T r = 1;
            bool over = false;
            for (unsigned int i = 0; i < b; ++i)
                over |= mul_checked<m, T>(r, a, r);
            result = r;
            return over;
        } else {
            result = boost::multiprecision::pow(a, b);
            return false;
        }
    }

    template<OverflowMode m, typename To, typename From>
    static constexpr bool cast_checked(const From &f, To &result) noexcept {
        if constexpr (std::is_integral_v<To> && std::is_integral_v<From> && m == OverflowMode::Checked) {
            if (!std::in_range<To>(f))
                return true;
        }
        result = static_cast<To>(f);
        return false;
    }

    static constexpr UnscaledValue_t abs(const UnscaledValue_t &value) noexcept {
        if constexpr (std::is_integral_v<UnscaledValue_t> && !std::is_signed_v<UnscaledValue_t>) {
            return value;
        } else {
            return value < 0 ? -value : value;
        }
    }

public:
    constexpr BigDecimal() noexcept : BigDecimal{0, 0} {
    }

    constexpr BigDecimal(const UnscaledValue_t &unscaled_value, Exponent_t exponent) noexcept
        : unscaled_value(unscaled_value), exponent(exponent) {}

    constexpr explicit BigDecimal(std::string_view value) : unscaled_value(0), exponent(0) {
        bool begin = true;
        bool decimal = false;
        bool neg = false;
        for (const char c : value) {
            if (begin) {
                begin = false;
                if (c == '-') {
                    neg = true;
                    continue;
                } else if (c == '+') {
                    continue;
                }
            }
            if (c == '.') {
                if (decimal) {
                    throw InvalidNode{"http://www.w3.org/2001/XMLSchema#decimal parsing error: more than one . found"};
                }
                decimal = true;
                continue;
            }
            if (c < '0' || c > '9') {
                throw InvalidNode{"http://www.w3.org/2001/XMLSchema#decimal parsing error: non-numeric char found"};
            }
            auto n = c - '0';
            if (mul_checked<OverflowMode::Checked>(unscaled_value, UnscaledValue_t{base}, unscaled_value)) {
                throw InvalidNode{"http://www.w3.org/2001/XMLSchema#decimal parsing error: unscaled_value overflow"};
            }
            if (add_checked<OverflowMode::Checked>(unscaled_value, UnscaledValue_t{n}, unscaled_value)) {
                throw InvalidNode{"http://www.w3.org/2001/XMLSchema#decimal parsing error: unscaled_value overflow"};
            }
            if (decimal) {
                if (add_checked<OverflowMode::Checked>(exponent, Exponent_t{1}, exponent)) {
                    throw InvalidNode{"http://www.w3.org/2001/XMLSchema#decimal parsing error: exponent overflow"};
                }
            }
        }
        if (unscaled_value == 0) {
            neg = false;
        }
        if (neg) {
            unscaled_value = -unscaled_value;
        }
    }

    constexpr explicit BigDecimal(uint32_t value) noexcept : BigDecimal(UnscaledValue_t{value}, 0) {}

    constexpr explicit BigDecimal(uint64_t value) noexcept : BigDecimal(UnscaledValue_t{value}, 0) {}

    constexpr explicit BigDecimal(int32_t value) noexcept : BigDecimal(static_cast<UnscaledValue_t>(value), 0) {}

    constexpr explicit BigDecimal(int64_t value) noexcept : BigDecimal(static_cast<UnscaledValue_t>(value), 0) {}

    constexpr explicit BigDecimal(const UnscaledValue_t &value) noexcept
        requires(!std::is_same_v<UnscaledValue_t, int32_t> && !std::is_same_v<UnscaledValue_t, int64_t>)
        : BigDecimal(value, 0) {}

    constexpr explicit BigDecimal(float value) : BigDecimal(static_cast<double>(value)) {}

    explicit BigDecimal(double value) : unscaled_value(0), exponent(0) {
        // most of the algorithm is from OpenJDK: https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/19fb8f93c59dfd791f62d41f332db9e306bc1422/src/java.base/share/classes/java/math/BigDecimal.java#L915
        if (std::isinf(value) || std::isnan(value))
            throw std::invalid_argument{"value is NaN or infinity"};
        // this might fail on anything that is not x86-32/64
        static_assert(std::endian::native == std::endian::little, "BigDecimal{double} is only tested on x86-32/64 and might not work on other systems");
        // double is an IEEE 754 64-bit floating point value
        // memory layout:
        // sign | exponent  | fraction
        // 63   | 62 ... 52 | 51 ... 0
        // see https://en.wikipedia.org/wiki/Double-precision_floating-point_format for more info (and a better graphic)
        auto v = std::bit_cast<uint64_t>(value);
        bool neg = (v >> 63) != 0;
        auto ex = static_cast<int>((v >> 52) & 0x7ffL);
        uint64_t significand = ex == 0
                                       ? (v & ((1L << 52) - 1)) << 1
                                       : (v & ((1L << 52) - 1)) | (1L << 52);
        ex -= 1075;
        if (significand == 0) {
            return;
        }
        while (significand & 1) {
            significand >>= 1;
            ++ex;
        }
        static constexpr const char *exc = "double to BigDecimal overflow";
        if (ex == 0) {
            unscaled_value = UnscaledValue_t{significand};
        } else if (ex < 0) {
            exponent = static_cast<Exponent_t>(-ex);
            UnscaledValue_t e{0};
            if (pow_checked<OverflowMode::Checked>(UnscaledValue_t{5}, -ex, e))
                throw std::overflow_error{exc};
            if (mul_checked<OverflowMode::Checked>(UnscaledValue_t{significand}, e, unscaled_value))
                throw std::overflow_error{exc};
        } else {
            UnscaledValue_t e{0};
            if (pow_checked<OverflowMode::Checked>(UnscaledValue_t{2}, ex, e))
                throw std::overflow_error{exc};
            if (mul_checked<OverflowMode::Checked>(UnscaledValue_t{significand}, e, unscaled_value))
                throw std::overflow_error{exc};
        }
        if (neg)
            unscaled_value = -unscaled_value;
        normalize();
    }

    constexpr void normalize() noexcept {
        normalize(unscaled_value, exponent);
    }

    static constexpr void normalize(UnscaledValue_t &unscaled_value, Exponent_t &exponent) noexcept {
        while (exponent > 0 && unscaled_value % base == 0) {
            unscaled_value /= base;
            --exponent;
        }
    }

    [[nodiscard]] constexpr Exponent_t get_exponent() const noexcept {
        return exponent;
    }

    [[nodiscard]] constexpr UnscaledValue_t get_unscaled_value() const noexcept {
        return unscaled_value;
    }

    [[nodiscard]] constexpr bool positive() const noexcept {
        return unscaled_value >= 0;
    }

private:
    // op_checked has to be add_checked or sub_checked with the same OverflowMode
    template<OverflowMode m, bool (*op_checked)(const UnscaledValue_t &t, const UnscaledValue_t &o, UnscaledValue_t &result)>
    constexpr bool add_or_sub(const BigDecimal &other, BigDecimal &result) const noexcept {
        UnscaledValue_t t = this->unscaled_value;
        UnscaledValue_t o = other.unscaled_value;
        Exponent_t new_exp = std::max(this->exponent, other.exponent);
        if (this->exponent < new_exp) {
            UnscaledValue_t ex{0};
            if (pow_checked<m>(UnscaledValue_t{base}, new_exp - this->exponent, ex))
                return true;
            if (mul_checked<m>(t, ex, t))
                return true;
        } else if (other.exponent < new_exp) {
            UnscaledValue_t ex{0};
            if (pow_checked<m>(UnscaledValue_t{base}, new_exp - other.exponent, ex))
                return true;
            if (mul_checked<m>(o, ex, o))
                return true;
        }
        UnscaledValue_t res = 0;
        if (op_checked(t, o, res))
            return true;
        result = BigDecimal{res, new_exp};
        return false;
    }

    template<OverflowMode m>
    constexpr bool mul(const BigDecimal &other, BigDecimal &result) const noexcept {
        UnscaledValue_t v{0};
        if (mul_checked<m>(this->unscaled_value, other.unscaled_value, v))
            return true;
        Exponent_t e{0};
        if (add_checked<m>(this->exponent, other.exponent, e))
            return true;
        result = BigDecimal{v, e};
        return false;
    }

    constexpr static BigDecimal handle_rounding(UnscaledValue_t v, Exponent_t e, UnscaledValue_t rem, RoundingMode m) noexcept {
        switch (m) {
            case RoundingMode::Trunc:
                return BigDecimal{v, e};
            case RoundingMode::Floor:
                if (v >= 0 || rem == 0)
                    return BigDecimal{v, e};
                else
                    return BigDecimal{v - 1, e};
            case RoundingMode::Ceil:
                if (v < 0 || rem == 0)
                    return BigDecimal{v, e};
                else
                    return BigDecimal{v + 1, e};
            case RoundingMode::Round:
                if (rem < 0)
                    rem = -rem;
                if (v < 0) {
                    if (rem >= 5)
                        return BigDecimal{v - 1, e};
                    else
                        return BigDecimal{v, e};
                } else {
                    if (rem >= 5)
                        return BigDecimal{v + 1, e};
                    else
                        return BigDecimal{v, e};
                }
            default:
                RDF4CPP_UNREACHABLE;
        }
    }

    template<OverflowMode m>
    constexpr bool div(const BigDecimal &other, Exponent_t max_scale_increase, RoundingMode mode, BigDecimal &result) const noexcept {
        if (this->unscaled_value == 0) {
            result = BigDecimal{0, 0};
            return false;
        }
        UnscaledValue_t t = this->unscaled_value;
        Exponent_t ex = this->exponent;
        UnscaledValue_t div = other.unscaled_value;
        if (ex >= other.exponent) {
            if (sub_checked<m>(ex, other.exponent, ex))
                return true;
        } else {
            Exponent_t tmp{0};
            if (sub_checked<m>(other.exponent, ex, tmp))
                return true;
            if (pow_checked<m>(Exponent_t{base}, tmp, tmp))
                return true;
            UnscaledValue_t tmp2{0};
            if (cast_checked<m>(tmp, tmp2))
                return true;
            if (mul_checked<m>(t, tmp2, t))
                return true;
            ex = 0;
        }
        UnscaledValue_t res = t / div;
        UnscaledValue_t rem = t % div;
        while (rem != 0) {
            if (max_scale_increase == 0) {
                if (mul_checked<m>(rem, UnscaledValue_t{base}, rem))
                    return true;
                result = handle_rounding(res, ex, rem / div, mode);
                return false;
            }
            if constexpr (std::is_integral_v<Exponent_t>) {
                if (ex == std::numeric_limits<Exponent_t>::max()) {
                    if (mul_checked<m>(rem, UnscaledValue_t{base}, rem))
                        return true;
                    result = handle_rounding(res, ex, rem / div, mode);
                    return false;
                }
            }
            ++ex;
            if (mul_checked<m>(res, UnscaledValue_t{base}, res))
                return true;
            if (mul_checked<m>(rem, UnscaledValue_t{base}, rem))
                return true;
            res += rem / div;
            rem = rem % div;
            --max_scale_increase;
        }
        result = BigDecimal{res, ex};
        return false;
    }

    template<OverflowMode m>
    constexpr bool pow(unsigned int n, BigDecimal &result) const noexcept {
        BigDecimal r{1};

        for (unsigned int i = 0; i < n; ++i) {
            if (r.mul<m>(*this, r))
                return true;
        }
        result = r;
        return false;
    }

    static double cast_unscaled_to_double_safe(UnscaledValue_t const &value) noexcept {
        // There is a bug in boost versions <1.86.0
        // that can cause a crash in the cast `static_cast<double>(cpp_int)`
        // https://github.com/boostorg/multiprecision/issues/553

#if BOOST_VERSION < 108600
        // workaround from https://github.com/scylladb/scylladb/commit/51d09e6a2a407ea9b9ad380ba30e5558a25bb8be#diff-81e6758bbedbe566a51706c5af1ea68be225c36feb68983c3de0a69138f01264R73
        static auto const min = UnscaledValue_t{std::numeric_limits<double>::lowest()};
        static auto const max = UnscaledValue_t{std::numeric_limits<double>::max()};
        if (value < min) {
            return -std::numeric_limits<double>::infinity();
        } else if (value > max) {
            return std::numeric_limits<double>::infinity();
        }
#endif // BOOST_VERSION

        return static_cast<double>(value);
    }

public:
    [[nodiscard]] constexpr BigDecimal operator-() const noexcept {
        return BigDecimal(-this->unscaled_value, this->exponent);
    }
    [[nodiscard]] constexpr nonstd::expected<BigDecimal, DecimalError> unary_minus_checked() const noexcept {
        if constexpr (std::is_integral_v<UnscaledValue_t>) {
            if (std::numeric_limits<UnscaledValue_t>::min() == unscaled_value)
                return nonstd::make_unexpected(DecimalError::Overflow);
        }
        return BigDecimal(-this->unscaled_value, this->exponent);
    }


    [[nodiscard]] constexpr BigDecimal operator+() const noexcept {
        return *this;
    }

    [[nodiscard]] constexpr BigDecimal operator+(const BigDecimal &other) const noexcept {
        BigDecimal res{0};
        add_or_sub<OverflowMode::UndefinedBehavior, add_checked<OverflowMode::UndefinedBehavior, UnscaledValue_t>>(other, res);
        return res;
    }
    [[nodiscard]] constexpr nonstd::expected<BigDecimal, DecimalError> add_checked(const BigDecimal &other) const noexcept {
        BigDecimal res{0};
        if (add_or_sub<OverflowMode::Checked, add_checked<OverflowMode::Checked, UnscaledValue_t>>(other, res))
            return nonstd::make_unexpected(DecimalError::Overflow);
        return res;
    }

    constexpr BigDecimal &operator+=(const BigDecimal &other) noexcept {
        *this = *this + other;
        return *this;
    }

    [[nodiscard]] constexpr BigDecimal operator-(const BigDecimal &other) const noexcept {
        BigDecimal res{0};
        add_or_sub<OverflowMode::UndefinedBehavior, sub_checked<OverflowMode::UndefinedBehavior, UnscaledValue_t>>(other, res);
        return res;
    }

    [[nodiscard]] constexpr nonstd::expected<BigDecimal, DecimalError> sub_checked(const BigDecimal &other) const noexcept {
        BigDecimal res{0};
        if (add_or_sub<OverflowMode::Checked, sub_checked<OverflowMode::Checked, UnscaledValue_t>>(other, res))
            return nonstd::make_unexpected(DecimalError::Overflow);
        return res;
    }

    constexpr BigDecimal operator-=(const BigDecimal &other) noexcept {
        *this = *this - other;
        return *this;
    }

    [[nodiscard]] constexpr BigDecimal operator*(const BigDecimal &other) const noexcept {
        BigDecimal res{0};
        mul<OverflowMode::UndefinedBehavior>(other, res);
        return res;
    }

    [[nodiscard]] constexpr nonstd::expected<BigDecimal, DecimalError> mul_checked(const BigDecimal &other) const noexcept {
        BigDecimal res{0};
        if (mul<OverflowMode::Checked>(other, res))
            return nonstd::make_unexpected(DecimalError::Overflow);
        return res;
    }

    constexpr BigDecimal &operator*=(const BigDecimal &other) noexcept {
        *this = *this * other;
        return *this;
    }

    [[nodiscard]] constexpr BigDecimal operator/(const BigDecimal &other) const noexcept {
        return div(other, 1000);
    }

    [[nodiscard]] constexpr BigDecimal div(const BigDecimal &other, Exponent_t max_scale_increase, RoundingMode mode = RoundingMode::Floor) const noexcept {
        if (other.unscaled_value == 0)
            return BigDecimal{0, 0};  // undefined behavior (cpp_int throws)
        BigDecimal res{0};
        div<OverflowMode::UndefinedBehavior>(other, max_scale_increase, mode, res);
        return res;
    }

    [[nodiscard]] constexpr nonstd::expected<BigDecimal, DecimalError> div_checked(const BigDecimal &other, Exponent_t max_scale_increase, RoundingMode mode = RoundingMode::Floor) const noexcept {
        if (other.unscaled_value == 0)
            return nonstd::make_unexpected(DecimalError::NotDefined);
        BigDecimal res{0};
        if (div<OverflowMode::Checked>(other, max_scale_increase, mode, res))
            return nonstd::make_unexpected(DecimalError::Overflow);
        return res;
    }

    constexpr BigDecimal &operator/=(const BigDecimal &other) noexcept {
        *this = *this / other;
        return *this;
    }

    [[nodiscard]] constexpr BigDecimal pow(unsigned int n) const noexcept {
        BigDecimal r{0};
        pow<OverflowMode::UndefinedBehavior>(n, r);
        return r;
    }

    [[nodiscard]] constexpr nonstd::expected<BigDecimal, DecimalError> pow_checked(unsigned int n) const noexcept {
        BigDecimal r{0};
        if (pow<OverflowMode::Checked>(n, r))
            return nonstd::make_unexpected(DecimalError::Overflow);
        return r;
    }

    [[nodiscard]] constexpr BigDecimal round(RoundingMode mode) const noexcept {
        if (exponent == 0)
            return *this;
        UnscaledValue_t v{base};
        if (pow_checked<OverflowMode::Checked>(v, exponent, v))
            return BigDecimal{0, 0};  // base pow exponent overflows and this did not, we have to be close to 0
        UnscaledValue_t rem = unscaled_value % v;
        rem = rem * 10 / v;
        v = unscaled_value / v;
        return handle_rounding(v, 0, rem, mode);
    }

    [[nodiscard]] constexpr BigDecimal abs() const noexcept {
        if (positive())
            return *this;
        else
            return -*this;
    }

    [[nodiscard]] constexpr nonstd::expected<BigDecimal, DecimalError> abs_checked() const noexcept {
        if (positive())
            return *this;
        else
            return this->unary_minus_checked();
    }

    constexpr std::strong_ordering operator<=>(const BigDecimal &other) const noexcept {
        if (this->positive() != other.positive())
            return this->positive() ? std::strong_ordering::greater : std::strong_ordering::less;
        UnscaledValue_t t = this->unscaled_value;
        UnscaledValue_t o = other.unscaled_value;
        if (this->exponent > other.exponent) {
            UnscaledValue_t b{base};
            if (pow_checked<OverflowMode::Checked>(b, this->exponent - other.exponent, b))
                return std::strong_ordering::less;  // t does fit into the same precision, while o does not
            if (mul_checked<OverflowMode::Checked>(o, b, o))
                return std::strong_ordering::less;  // t does fit into the same precision, while o does not
        } else if (this->exponent < other.exponent) {
            UnscaledValue_t b{base};
            if (pow_checked<OverflowMode::Checked>(b, other.exponent - this->exponent, b))
                return std::strong_ordering::greater;  // o does fit into the same precision, while t does not
            if (mul_checked<OverflowMode::Checked>(t, b, t))
                return std::strong_ordering::greater;  // o does fit into the same precision, while t does not
        }
        if (t < o)
            return std::strong_ordering::less;
        else if (t > o)
            return std::strong_ordering::greater;
        else
            return std::strong_ordering::equivalent;
    };
    constexpr bool operator==(const BigDecimal &other) const noexcept {
        return *this <=> other == std::strong_ordering::equivalent;
    }
    friend bool operator==(const BigDecimal &t, int other) noexcept {
        return t == BigDecimal{other, 0};
    }
    friend bool operator==(int t, const BigDecimal &other) noexcept {
        return other == t;
    }

    [[nodiscard]] explicit operator double() const noexcept {
        double const v = cast_unscaled_to_double_safe(unscaled_value) * std::pow(static_cast<double>(base), -static_cast<double>(exponent));
        if (!std::isnan(v) && !std::isinf(v))
            return v;
        // even Javas BigDecimal has no better solution
        auto const s = static_cast<std::string>(*this);
        return std::stod(s);
    }

    [[nodiscard]] explicit operator float() const noexcept {
        return static_cast<float>(static_cast<double>(*this));
    }

    [[nodiscard]] constexpr explicit operator UnscaledValue_t() const noexcept {
        if (exponent == 0)
            return unscaled_value;
        return round(RoundingMode::Trunc).unscaled_value;
    }

    [[nodiscard]] explicit operator std::string() const noexcept {
        if (unscaled_value == 0)
            return "0.0";
        std::stringstream s{};
        UnscaledValue_t v = unscaled_value;
        Exponent_t ex = exponent;
        bool hasDot = false;
        while (v != 0) {
            if (!hasDot && ex == 0) {
                if (s.view().empty())
                    s << '0';
                s << '.';
                hasDot = true;
            } else {
                --ex;
            }
            using namespace std;
            auto c = static_cast<uint32_t>(abs(v % base));
            if (hasDot || c != 0 || !s.view().empty())  // skip trailing 0s
                s << c;
            v /= base;
        }
        if (!hasDot) {
            for (Exponent_t i = 0; i < ex; ++i)
                s << '0';
            s << ".0";
        }
        if (!positive())
            s << '-';
        std::string_view sv = s.view();
        return std::string{sv.rbegin(), sv.rend()};
    }

    friend std::ostream &operator<<(std::ostream &str, const BigDecimal &bn) {
        auto s = static_cast<std::string>(bn);
        str << s;
        return str;
    }

    template<dice::hash::Policies::HashPolicy Policy = dice::hash::Policies::wyhash>
    [[nodiscard]] size_t hash() const {
        BigDecimal n = *this;
        n.normalize();

        return dice::hash::dice_hash_templates<Policy>::dice_hash(std::tie(n.unscaled_value, n.exponent));
    }
};

template<typename UnscaledValue_t, typename Exponent_t>
std::string to_string(const BigDecimal<UnscaledValue_t, Exponent_t> &r) noexcept {
    return static_cast<std::string>(r);
}

template<typename UnscaledValue_t, typename Exponent_t>
BigDecimal<UnscaledValue_t, Exponent_t> pow(const BigDecimal<UnscaledValue_t, Exponent_t> &r, unsigned int n) noexcept {
    return r.pow(n);
}
template<typename UnscaledValue_t, typename Exponent_t>
BigDecimal<UnscaledValue_t, Exponent_t> round(const BigDecimal<UnscaledValue_t, Exponent_t> &r) noexcept {
    return r.round(RoundingMode::Round);
}
template<typename UnscaledValue_t, typename Exponent_t>
BigDecimal<UnscaledValue_t, Exponent_t> floor(const BigDecimal<UnscaledValue_t, Exponent_t> &r) noexcept {
    return r.round(RoundingMode::Floor);
}
template<typename UnscaledValue_t, typename Exponent_t>
BigDecimal<UnscaledValue_t, Exponent_t> ceil(const BigDecimal<UnscaledValue_t, Exponent_t> &r) noexcept {
    return r.round(RoundingMode::Ceil);
}
template<typename UnscaledValue_t, typename Exponent_t>
BigDecimal<UnscaledValue_t, Exponent_t> trunc(const BigDecimal<UnscaledValue_t, Exponent_t> &r) noexcept {
    return r.round(RoundingMode::Trunc);
}
template<typename UnscaledValue_t, typename Exponent_t>
BigDecimal<UnscaledValue_t, Exponent_t> abs(const BigDecimal<UnscaledValue_t, Exponent_t> &r) noexcept {
    return r.abs();
}
}  // namespace rdf4cpp

#ifndef DOXYGEN_PARSER
template<typename UnscaledValue_t, typename Exponent_t>
struct std::hash<rdf4cpp::BigDecimal<UnscaledValue_t, Exponent_t>> {
    size_t operator()(const rdf4cpp::BigDecimal<UnscaledValue_t, Exponent_t> &r) const {
        return r.hash();
    }
};

template<typename Policy>
struct dice::hash::dice_hash_overload<Policy, ::boost::multiprecision::cpp_int> {
    static size_t dice_hash(::boost::multiprecision::cpp_int const &x) noexcept {
        return dice::hash::dice_hash_templates<Policy>::dice_hash(std::hash<::boost::multiprecision::cpp_int>{}(x));
    }
};

template<typename Policy, typename U, typename E>
struct dice::hash::dice_hash_overload<Policy, rdf4cpp::BigDecimal<U, E>> {
    static size_t dice_hash(rdf4cpp::BigDecimal<U, E> const &x) noexcept {
        return x.template hash<Policy>();
    }
};
#endif

template<typename UnscaledValue_t, typename Exponent_t>
class std::numeric_limits<rdf4cpp::BigDecimal<UnscaledValue_t, Exponent_t>> {
public:
    using BigDecimal = rdf4cpp::BigDecimal<UnscaledValue_t, Exponent_t>;

    static constexpr bool is_specialized = true;
    static constexpr bool is_signed = true;
    static constexpr bool is_integer = false;
    static constexpr bool is_exact = true;
    static constexpr bool has_infinity = false;
    static constexpr bool has_quiet_NaN = false;
    static constexpr bool has_signaling_NaN = false;
    static constexpr std::float_denorm_style has_denorm = std::denorm_absent;
    static constexpr bool has_denorm_loss = false;
    static constexpr std::float_round_style round_style = std::round_toward_zero;
    static constexpr bool is_iec559 = false;
    static constexpr bool is_bounded = true;
    static constexpr bool is_modulo = false;
    static constexpr int digits = numeric_limits<UnscaledValue_t>::digits;
    static constexpr int digits10 = numeric_limits<UnscaledValue_t>::digits10;
    static constexpr int max_digits10 = numeric_limits<UnscaledValue_t>::max_digits10;
    static constexpr int radix = 2;
    static constexpr int min_exponent = 0;
    static constexpr int min_exponent10 = 0;
    static constexpr int max_exponent = 0;
    static constexpr int max_exponent10 = 0;
    static constexpr bool traps = false;
    static constexpr bool tinyness_before = false;
    static constexpr BigDecimal max() noexcept {
        return BigDecimal{numeric_limits<UnscaledValue_t>::max(), 0};
    }
    static constexpr BigDecimal min() noexcept {
        return BigDecimal{numeric_limits<UnscaledValue_t>::min(), 0};
    }
    static constexpr BigDecimal lowest() noexcept {
        return min();
    }
    static constexpr BigDecimal epsilon() noexcept {
        return BigDecimal{1, numeric_limits<Exponent_t>::max()};
    }
    static constexpr BigDecimal round_error() noexcept {
        return BigDecimal{0};
    }
    static constexpr BigDecimal infinity() noexcept {
        return 0;
    }
    static constexpr BigDecimal quiet_NaN() noexcept {
        return 0;
    }
    static constexpr BigDecimal signaling_NaN() noexcept {
        return 0;
    }
    static constexpr BigDecimal denorm_min() noexcept {
        return 0;
    }
};

#endif  //RDF4CPP_BIGDECIMAL_H