diff --git a/.github/workflows/run_test_and_examples.yml b/.github/workflows/run_test_and_examples.yml index 7bfd8b262..8bace958b 100644 --- a/.github/workflows/run_test_and_examples.yml +++ b/.github/workflows/run_test_and_examples.yml @@ -14,8 +14,6 @@ jobs: - os: ubuntu-24.04-arm compiler: gcc-14 - - os: ubuntu-24.04-arm - compiler: clang-19 - os: ubuntu-24.04-arm compiler: clang-20 diff --git a/.github/workflows/run_test_for_coverage.yml b/.github/workflows/run_test_for_coverage.yml index df6e81c6a..bc4723945 100644 --- a/.github/workflows/run_test_for_coverage.yml +++ b/.github/workflows/run_test_for_coverage.yml @@ -7,7 +7,7 @@ jobs: strategy: matrix: config: - - os: ubuntu-22.04 + - os: ubuntu-24.04 compiler: gcc-13 fail-fast: false name: ${{ matrix.config.compiler }}, ${{ matrix.config.os }} diff --git a/README.md b/README.md index b91ed1caf..e0ec6c2aa 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,26 @@ cd build_dir sudo make install ``` +### Limits for Datatypes +By default, unlimited precision datatypes are limited in accordance with https://www.w3.org/TR/xmlschema11-2/#partial-implementation . + +For `http://www.w3.org/2001/XMLSchema#integer` +(and the related: `http://www.w3.org/2001/XMLSchema#nonNegativeInteger` +`http://www.w3.org/2001/XMLSchema#positiveInteger` `http://www.w3.org/2001/XMLSchema#nonPositiveInteger` +`http://www.w3.org/2001/XMLSchema#negativeInteger`) this limit is a signed 128-bit integer +with the usual range of `[-2^127,2^127-1]` + +And `http://www.w3.org/2001/XMLSchema#decimal` is composed of the following parts: `i/10^k`, +where `i` is a signed 128-bit integer (`[-2^127,2^127-1]`) and `k` is an unsigned 64-bit integer (`[0,2^64]`). + +For `http://www.w3.org/2001/XMLSchema#dateTime` (and all derived types) there are 2 limits: +- represented as a time point with nanosecond precision with a 128-bit signed integer +- the year part alone in a 64-bit signed integer +Both limits are enough to cover both the current best theories of the big bang and the projected heat death of the universe. + +For `http://www.w3.org/2001/XMLSchema#duration` (and all derived types), both parts have a separate signed 64-bit integer +and with it its associated limits. The seconds part of the duration supports nanosecond precision. + ### Additional CMake config options: - `-DBUILD_EXAMPLES=ON/OFF [default: OFF]`: Build the examples. @@ -121,11 +141,11 @@ sudo make install ## Supported Platforms -- **Linux distributions (x86-64, AArch64)** (e.g. Ubuntu>=24.04, Fedora>=41, etc.) with: - - GCC>=14 (libstdc++>=14; used with both GCC and Clang) - - Clang>=19 - - glibc 2.35+ or musl 1.2.4+ -- **macOS (ARM64)**: macOS Sonoma (14)+ with GCC>=14 (via Homebrew) +- **Linux distributions (x86-64, aarch64)** (e.g. Ubuntu>=24.04, Fedora>=41, etc.) with: + - gcc>=14 (libstdc++>=14; used with both GCC and Clang) + - clang>=19* (on aarch64 clang>=20 is required) + - glibc>=2.35 or musl>=1.2.4 +- **macOS (aarch64)**: macOS Sonoma (>=14) with GCC>=14 (via Homebrew) ## Stability diff --git a/docs/source/getting_started.rst b/docs/source/getting_started.rst index df176808d..7cbda410e 100644 --- a/docs/source/getting_started.rst +++ b/docs/source/getting_started.rst @@ -71,4 +71,3 @@ ________________________________ * :code:`-DBUILD_EXAMPLES=ON/OFF [default: OFF]`: Build the examples. * :code:`-DBUILD_TESTING=ON/OFF [default: OFF]`: Build the tests. * :code:`-DBUILD_SHARED_LIBS=ON/OFF [default: OFF]`: Build a shared library instead of a static one. -* :code:`-DUSE_CONAN=ON/OFF [default: ON]`: If available, use Conan to retrieve dependencies. diff --git a/docs/source/users_guide.rst b/docs/source/users_guide.rst index 7fb5c7895..0b22cde77 100644 --- a/docs/source/users_guide.rst +++ b/docs/source/users_guide.rst @@ -36,6 +36,31 @@ You can find all supported Datatypes here: * :ref:`namespace_rdf4cpp__datatypes__rdf`: RDF datatypes (LangString). * :ref:`namespace_rdf4cpp__datatypes__owl`: OWL datatypes. + +Limits for Datatypes +++++++++++++++++++++ +By default, unlimited precision datatypes are limited in accordance with https://www.w3.org/TR/xmlschema11-2/#partial-implementation . + +For :code:`http://www.w3.org/2001/XMLSchema#integer` +(and the related: :code:`http://www.w3.org/2001/XMLSchema#nonNegativeInteger` +:code:`http://www.w3.org/2001/XMLSchema#positiveInteger` :code:`http://www.w3.org/2001/XMLSchema#nonPositiveInteger` +:code:`http://www.w3.org/2001/XMLSchema#negativeInteger`) this limit is a signed 128-bit integer +with the usual range of :code:`[-2^127,2^127-1]` + +And :code:`http://www.w3.org/2001/XMLSchema#decimal` is composed of the following parts: :code:`i/10^k`, +where :code:`i` is a signed 128-bit integer (:code:`[-2^127,2^127-1]`) and :code:`k` is an unsigned 64-bit integer (:code:`[0,2^64]`). +ss +For :code:`http://www.w3.org/2001/XMLSchema#dateTime` (and all derived types) there are 2 limits: + +* represented as a time point with nanosecond precision with a 128-bit signed integer +* the year part alone in a 64-bit signed integer + +Both limits are enough to cover both the current best theories of the big bang and the projected heat death of the universe. + +For :code:`http://www.w3.org/2001/XMLSchema#duration` (and all derived types), both parts have a separate signed 64-bit integer +and with it its associated limits. The seconds part of the duration supports nanosecond precision. + + Parsing Files ------------- diff --git a/src/rdf4cpp/BigDecimal.hpp b/src/rdf4cpp/BigDecimal.hpp index f4dacada6..1a1273002 100644 --- a/src/rdf4cpp/BigDecimal.hpp +++ b/src/rdf4cpp/BigDecimal.hpp @@ -1,6 +1,8 @@ #ifndef RDF4CPP_BIGDECIMAL_H #define RDF4CPP_BIGDECIMAL_H +#include +#include #include #include #include @@ -11,897 +13,895 @@ #include #include +#include #include namespace rdf4cpp { -enum struct RoundingMode { - Floor, - Ceil, - Round, - Trunc, -}; - -enum struct DecimalError { - Overflow, - NotDefined, // aka NotANumber -}; - -template -concept BigDecimalBaseType = std::numeric_limits::is_specialized && !std::floating_point; - -template - requires(!std::signed_integral && !std::unsigned_integral) -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; + namespace util { + enum struct RoundingMode { + Floor, + Ceil, + Round, + Trunc, + }; + + enum struct DecimalError { + Overflow, + NotDefined, // aka NotANumber + }; + + template + concept BigDecimalBaseType = std::numeric_limits::is_specialized && !std::floating_point; + + template + requires(!std::signed_integral && !std::unsigned_integral) + 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; + + using OverflowMode = detail::OverflowMode; + + static constexpr uint32_t base = 10; + + static constexpr UnscaledValue_t abs(UnscaledValue_t const &value) noexcept { + if constexpr (std::is_integral_v && !std::is_signed_v) { + return value; + } else { + return value < 0 ? -value : value; + } + } - static constexpr uint32_t base = 10; + public: + constexpr BigDecimal() noexcept : BigDecimal{0, 0} { + } - enum struct OverflowMode { - Checked, - UndefinedBehavior, - }; + /** + * creates a BigDecimal from its components. + * it has the value of unscaled_value * pow(10, -exponent). + * @param unscaled_value + * @param exponent + */ + constexpr BigDecimal(UnscaledValue_t const &unscaled_value, Exponent_t exponent) noexcept + : unscaled_value(unscaled_value), exponent(exponent) { + } - template - static constexpr bool add_checked(const T &a, const T &b, T &result) noexcept { - if constexpr (std::is_integral_v && m == OverflowMode::Checked) { - return __builtin_add_overflow(a, b, &result); - } else { - result = a + b; - return false; - } - } + /** + * parses a BigDecimal from a string_view. + * may include a leading sign and one decimal point ., everything else needs to be numeric. + * @param value + * @throw rdf4cpp::InvalidNode if a invalid char is found, or on exceeding the types numeric limits + */ + constexpr explicit BigDecimal(std::string_view value) : unscaled_value(0), exponent(0) { + bool begin = true; + bool decimal = false; + bool neg = false; + for (char const 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 (detail::mul_checked(unscaled_value, UnscaledValue_t{base}, unscaled_value)) { + throw InvalidNode{"http://www.w3.org/2001/XMLSchema#decimal parsing error: unscaled_value overflow"}; + } + if (detail::add_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 (detail::add_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; + } + } - template - static constexpr bool sub_checked(const T &a, const T &b, T &result) noexcept { - if constexpr (std::is_integral_v && m == OverflowMode::Checked) { - return __builtin_sub_overflow(a, b, &result); - } else { - result = a - b; - return false; - } - } + /** + * converts a uint32_t to a BigDecimal + * @param value + */ + constexpr explicit BigDecimal(uint32_t value) noexcept : BigDecimal(UnscaledValue_t{value}, 0) { + } - template - static constexpr bool mul_checked(const T &a, const T &b, T &result) noexcept { - if constexpr (std::is_integral_v && m == OverflowMode::Checked) { - return __builtin_mul_overflow(a, b, &result); - } else { - result = a * b; - return false; - } - } + /** + * converts a uint64_t to a BigDecimal + * @param value + */ + constexpr explicit BigDecimal(uint64_t value) noexcept : BigDecimal(UnscaledValue_t{value}, 0) { + } - template - static constexpr bool pow_checked(const T &a, unsigned int b, T &result) noexcept { - if constexpr (std::is_integral_v) { - T r = 1; - bool over = false; - for (unsigned int i = 0; i < b; ++i) - over |= mul_checked(r, a, r); - result = r; - return over; - } else { - result = boost::multiprecision::pow(a, b); - return false; - } - } + /** + * converts a int32_t to a BigDecimal + * @param value + */ + constexpr explicit BigDecimal(int32_t value) noexcept : BigDecimal(static_cast(value), 0) { + } - template - static constexpr bool cast_checked(const From &f, To &result) noexcept { - if constexpr (std::is_integral_v && std::is_integral_v && m == OverflowMode::Checked) { - if (!std::in_range(f)) - return true; - } - result = static_cast(f); - return false; - } + /** + * converts a int64_t to a BigDecimal + * @param value + */ + constexpr explicit BigDecimal(int64_t value) noexcept : BigDecimal(static_cast(value), 0) { + } - static constexpr UnscaledValue_t abs(const UnscaledValue_t &value) noexcept { - if constexpr (std::is_integral_v && !std::is_signed_v) { - return value; - } else { - return value < 0 ? -value : value; - } - } + /** + * converts a UnscaledValue_t to a BigDecimal + * @param value + */ + constexpr explicit BigDecimal(UnscaledValue_t const &value) noexcept + requires(!std::is_same_v && !std::is_same_v) + : BigDecimal(value, 0) { + } -public: - constexpr BigDecimal() noexcept : BigDecimal{0, 0} { - } + /** + * converts a float to a BigDecimal. + * this conversion might not be exact, due to the built in limitations of floats. + * if you have the possibility, use one of the other constructors. + * @param value + * @throw std::overflow_error on exceeding the types numeric limits + */ + constexpr explicit BigDecimal(float value) : BigDecimal(static_cast(value)) { + } - /** - * creates a BigDecimal from its components. - * it has the value of unscaled_value * pow(10, -exponent). - * @param unscaled_value - * @param exponent - */ - constexpr BigDecimal(const UnscaledValue_t &unscaled_value, Exponent_t exponent) noexcept - : unscaled_value(unscaled_value), exponent(exponent) {} - - /** - * parses a BigDecimal from a string_view. - * may include a leading sign and one decimal point ., everything else needs to be numeric. - * @param value - * @throw rdf4cpp::InvalidNode if a invalid char is found, or on exceeding the types numeric limits - */ - 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; + private: + void from_double_direct(double value) + requires(std::numeric_limits::digits >= std::numeric_limits::digits) + { + // 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(value); + bool const neg = (v >> 63) != 0; + auto ex = static_cast((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; } - } - if (c == '.') { - if (decimal) { - throw InvalidNode{"http://www.w3.org/2001/XMLSchema#decimal parsing error: more than one . found"}; + while ((significand & 1) == 0) { + significand >>= 1; + ++ex; } - 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(unscaled_value, UnscaledValue_t{base}, unscaled_value)) { - throw InvalidNode{"http://www.w3.org/2001/XMLSchema#decimal parsing error: unscaled_value overflow"}; - } - if (add_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(exponent, Exponent_t{1}, exponent)) { - throw InvalidNode{"http://www.w3.org/2001/XMLSchema#decimal parsing error: exponent overflow"}; + static constexpr char const *exc = "double to BigDecimal overflow"; + if (ex == 0) { + unscaled_value = UnscaledValue_t{significand}; + } else if (ex < 0) { + exponent = static_cast(-ex); + UnscaledValue_t e{0}; + if (detail::pow_checked(UnscaledValue_t{5}, -ex, e)) + throw std::overflow_error{exc}; + if (detail::mul_checked(UnscaledValue_t{significand}, e, unscaled_value)) + throw std::overflow_error{exc}; + } else { + UnscaledValue_t e{0}; + if (detail::pow_checked(UnscaledValue_t{2}, ex, e)) + throw std::overflow_error{exc}; + if (detail::mul_checked(UnscaledValue_t{significand}, e, unscaled_value)) + throw std::overflow_error{exc}; } + if (neg) + unscaled_value = -unscaled_value; + normalize(); } - } - if (unscaled_value == 0) { - neg = false; - } - if (neg) { - unscaled_value = -unscaled_value; - } - } - /** - * converts a uint32_t to a BigDecimal - * @param value - */ - constexpr explicit BigDecimal(uint32_t value) noexcept : BigDecimal(UnscaledValue_t{value}, 0) {} - - /** - * converts a uint64_t to a BigDecimal - * @param value - */ - constexpr explicit BigDecimal(uint64_t value) noexcept : BigDecimal(UnscaledValue_t{value}, 0) {} - - /** - * converts a int32_t to a BigDecimal - * @param value - */ - constexpr explicit BigDecimal(int32_t value) noexcept : BigDecimal(static_cast(value), 0) {} - - /** - * converts a int64_t to a BigDecimal - * @param value - */ - constexpr explicit BigDecimal(int64_t value) noexcept : BigDecimal(static_cast(value), 0) {} - - /** - * converts a UnscaledValue_t to a BigDecimal - * @param value - */ - constexpr explicit BigDecimal(const UnscaledValue_t &value) noexcept - requires(!std::is_same_v && !std::is_same_v) - : BigDecimal(value, 0) {} - - /** - * converts a float to a BigDecimal. - * this conversion might not be exact, due to the built in limitations of floats. - * if you have the possibility, use one of the other constructors. - * @param value - * @throw std::overflow_error on exceeding the types numeric limits - */ - constexpr explicit BigDecimal(float value) : BigDecimal(static_cast(value)) {} - - /** - * converts a double to a BigDecimal. - * this conversion might not be exact, due to the built in limitations of doubles. - * if you have the possibility, use one of the other constructors. - * @param value - * @throw std::overflow_error on exceeding the types numeric limits - */ - 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(value); - bool neg = (v >> 63) != 0; - auto ex = static_cast((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(-ex); - UnscaledValue_t e{0}; - if (pow_checked(UnscaledValue_t{5}, -ex, e)) - throw std::overflow_error{exc}; - if (mul_checked(UnscaledValue_t{significand}, e, unscaled_value)) - throw std::overflow_error{exc}; - } else { - UnscaledValue_t e{0}; - if (pow_checked(UnscaledValue_t{2}, ex, e)) - throw std::overflow_error{exc}; - if (mul_checked(UnscaledValue_t{significand}, e, unscaled_value)) - throw std::overflow_error{exc}; - } - if (neg) - unscaled_value = -unscaled_value; - normalize(); - } - - /** - * converts this BigDecimal to its smallest internal representation. - */ - constexpr void normalize() noexcept { - normalize(unscaled_value, exponent); - } - - /** - * converts this BigDecimal to its smallest internal representation. - */ - static constexpr void normalize(UnscaledValue_t &unscaled_value, Exponent_t &exponent) noexcept { - while (exponent > 0 && unscaled_value % base == 0) { - unscaled_value /= base; - --exponent; - } - } + public: + /** + * converts a double to a BigDecimal. + * this conversion might not be exact, due to the built in limitations of doubles. + * if you have the possibility, use one of the other constructors. + * @param value + * @throw std::overflow_error on exceeding the types numeric limits + */ + explicit BigDecimal(double value) : unscaled_value(0), exponent(0) { + if constexpr (!std::numeric_limits::is_bounded) { + from_double_direct(value); + } else { + BigDecimal v{value}; + while (true) { + if (detail::cast_checked(v.get_unscaled_value(), unscaled_value) || detail::cast_checked(v.get_exponent(), exponent)) { + if (v.get_exponent() > 0) { + v = BigDecimal{v.get_unscaled_value() / base, v.get_exponent() - 1}; + continue; + } + throw std::overflow_error{"double -> decimal unscaled value overflow"}; + } + return; + } + } + } - [[nodiscard]] constexpr Exponent_t get_exponent() const noexcept { - return exponent; - } + /** + * converts this BigDecimal to its smallest internal representation. + */ + constexpr void normalize() noexcept { + normalize(unscaled_value, exponent); + } - [[nodiscard]] constexpr UnscaledValue_t get_unscaled_value() const noexcept { - return unscaled_value; - } + /** + * converts this BigDecimal to its smallest internal representation. + */ + 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 bool positive() const noexcept { - return unscaled_value >= 0; - } + [[nodiscard]] constexpr Exponent_t get_exponent() const noexcept { + return exponent; + } -private: - // op_checked has to be add_checked or sub_checked with the same OverflowMode - template - 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(UnscaledValue_t{base}, new_exp - this->exponent, ex)) - return true; - if (mul_checked(t, ex, t)) - return true; - } else if (other.exponent < new_exp) { - UnscaledValue_t ex{0}; - if (pow_checked(UnscaledValue_t{base}, new_exp - other.exponent, ex)) - return true; - if (mul_checked(o, ex, o)) - return true; - } - UnscaledValue_t res = 0; - if (op_checked(t, o, res)) - return true; - result = BigDecimal{res, new_exp}; - return false; - } + [[nodiscard]] constexpr UnscaledValue_t get_unscaled_value() const noexcept { + return unscaled_value; + } - template - constexpr bool mul(const BigDecimal &other, BigDecimal &result) const noexcept { - UnscaledValue_t v{0}; - if (mul_checked(this->unscaled_value, other.unscaled_value, v)) - return true; - Exponent_t e{0}; - if (add_checked(this->exponent, other.exponent, e)) - return true; - result = BigDecimal{v, e}; - return false; - } + [[nodiscard]] constexpr bool positive() const noexcept { + return unscaled_value >= 0; + } - 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}; + private: + // op_checked has to be add_checked or sub_checked with the same OverflowMode + template + constexpr bool add_or_sub(BigDecimal const &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 (detail::pow_checked(UnscaledValue_t{base}, new_exp - this->exponent, ex)) + return true; + if (detail::mul_checked(t, ex, t)) + return true; + } else if (other.exponent < new_exp) { + UnscaledValue_t ex{0}; + if (detail::pow_checked(UnscaledValue_t{base}, new_exp - other.exponent, ex)) + return true; + if (detail::mul_checked(o, ex, o)) + return true; } - default: - RDF4CPP_UNREACHABLE; - } - } + UnscaledValue_t res = 0; + if (op_checked(t, o, res)) + return true; + result = BigDecimal{res, new_exp}; + return false; + } - template - 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(ex, other.exponent, ex)) - return true; - } else { - Exponent_t tmp{0}; - if (sub_checked(other.exponent, ex, tmp)) - return true; - if (pow_checked(Exponent_t{base}, tmp, tmp)) - return true; - UnscaledValue_t tmp2{0}; - if (cast_checked(tmp, tmp2)) - return true; - if (mul_checked(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(rem, UnscaledValue_t{base}, rem)) + template + constexpr bool mul(BigDecimal const &other, BigDecimal &result) const noexcept { + UnscaledValue_t v{0}; + if (detail::mul_checked(this->unscaled_value, other.unscaled_value, v)) + return true; + Exponent_t e{0}; + if (detail::add_checked(this->exponent, other.exponent, e)) return true; - result = handle_rounding(res, ex, rem / div, mode); + result = BigDecimal{v, e}; return false; } - if constexpr (std::is_integral_v) { - if (ex == std::numeric_limits::max()) { - if (mul_checked(rem, UnscaledValue_t{base}, rem)) - return true; - result = handle_rounding(res, ex, rem / div, mode); + + static constexpr 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 + constexpr bool div(BigDecimal const &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 (detail::sub_checked(ex, other.exponent, ex)) + return true; + } else { + Exponent_t tmp{0}; + if (detail::sub_checked(other.exponent, ex, tmp)) + return true; + if (detail::pow_checked(Exponent_t{base}, tmp, tmp)) + return true; + UnscaledValue_t tmp2{0}; + if (detail::cast_checked(tmp, tmp2)) + return true; + if (detail::mul_checked(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 (detail::mul_checked(rem, UnscaledValue_t{base}, rem)) + return true; + result = handle_rounding(res, ex, rem / div, mode); + return false; + } + if constexpr (std::is_integral_v) { + if (ex == std::numeric_limits::max()) { + if (detail::mul_checked(rem, UnscaledValue_t{base}, rem)) + return true; + result = handle_rounding(res, ex, rem / div, mode); + return false; + } + } + ++ex; + if (detail::mul_checked(res, UnscaledValue_t{base}, res)) + return true; + if (detail::mul_checked(rem, UnscaledValue_t{base}, rem)) + return true; + res += rem / div; + rem = rem % div; + --max_scale_increase; + } + result = BigDecimal{res, ex}; + return false; } - ++ex; - if (mul_checked(res, UnscaledValue_t{base}, res)) - return true; - if (mul_checked(rem, UnscaledValue_t{base}, rem)) - return true; - res += rem / div; - rem = rem % div; - --max_scale_increase; - } - result = BigDecimal{res, ex}; - return false; - } - template - constexpr bool pow(unsigned int n, BigDecimal &result) const noexcept { - BigDecimal r{1}; + template + constexpr bool pow(unsigned int n, BigDecimal &result) const noexcept { + BigDecimal r{1}; - for (unsigned int i = 0; i < n; ++i) { - if (r.mul(*this, r)) - return true; - } - result = r; - return false; - } + for (unsigned int i = 0; i < n; ++i) { + if (r.mul(*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(cpp_int)` - // https://github.com/boostorg/multiprecision/issues/553 + 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(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::lowest()}; - static auto const max = UnscaledValue_t{std::numeric_limits::max()}; - if (value < min) { - return -std::numeric_limits::infinity(); - } else if (value > max) { - return std::numeric_limits::infinity(); - } -#endif // BOOST_VERSION + // workaround from https://github.com/scylladb/scylladb/commit/51d09e6a2a407ea9b9ad380ba30e5558a25bb8be#diff-81e6758bbedbe566a51706c5af1ea68be225c36feb68983c3de0a69138f01264R73 + static auto const min = UnscaledValue_t{std::numeric_limits::lowest()}; + static auto const max = UnscaledValue_t{std::numeric_limits::max()}; + if (value < min) { + return -std::numeric_limits::infinity(); + } else if (value > max) { + return std::numeric_limits::infinity(); + } +#endif // BOOST_VERSION - return static_cast(value); - } + return static_cast(value); + } -public: - /** - * unary minus of this BigDecimal. - * may overflow. - * @return - */ - [[nodiscard]] constexpr BigDecimal operator-() const noexcept { - return BigDecimal(-this->unscaled_value, this->exponent); - } - /** - * unary minus of this BigDecimal. - * checks overflow. - * @return - */ - [[nodiscard]] constexpr nonstd::expected unary_minus_checked() const noexcept { - if constexpr (std::is_integral_v) { - if (std::numeric_limits::min() == unscaled_value) - return nonstd::make_unexpected(DecimalError::Overflow); - } - return BigDecimal(-this->unscaled_value, this->exponent); - } + public: + /** + * unary minus of this BigDecimal. + * may overflow. + * @return + */ + [[nodiscard]] constexpr BigDecimal operator-() const noexcept { + return BigDecimal(-this->unscaled_value, this->exponent); + } + /** + * unary minus of this BigDecimal. + * checks overflow. + * @return + */ + [[nodiscard]] constexpr nonstd::expected unary_minus_checked() const noexcept { + if constexpr (std::is_integral_v) { + if (std::numeric_limits::min() == unscaled_value) + return nonstd::make_unexpected(DecimalError::Overflow); + } + return BigDecimal(-this->unscaled_value, this->exponent); + } - /** - * unary plus (nop) of this BigDecimal. - * @return - */ - [[nodiscard]] constexpr BigDecimal operator+() const noexcept { - return *this; - } + /** + * unary plus (nop) of this BigDecimal. + * @return + */ + [[nodiscard]] constexpr BigDecimal operator+() const noexcept { + return *this; + } - /** - * addition of two BigDecimals. - * may overflow. - * @param other - * @return - */ - [[nodiscard]] constexpr BigDecimal operator+(const BigDecimal &other) const noexcept { - BigDecimal res{0}; - add_or_sub>(other, res); - return res; - } - /** - * addition of two BigDecimals. - * checks overflow. - * @param other - * @return - */ - [[nodiscard]] constexpr nonstd::expected add_checked(const BigDecimal &other) const noexcept { - BigDecimal res{0}; - if (add_or_sub>(other, res)) - return nonstd::make_unexpected(DecimalError::Overflow); - return res; - } + /** + * addition of two BigDecimals. + * may overflow. + * @param other + * @return + */ + [[nodiscard]] constexpr BigDecimal operator+(BigDecimal const &other) const noexcept { + BigDecimal res{0}; + add_or_sub>(other, res); + return res; + } + /** + * addition of two BigDecimals. + * checks overflow. + * @param other + * @return + */ + [[nodiscard]] constexpr nonstd::expected add_checked(BigDecimal const &other) const noexcept { + BigDecimal res{0}; + if (add_or_sub>(other, res)) + return nonstd::make_unexpected(DecimalError::Overflow); + return res; + } - /** - * addition of two BigDecimals. - * may overflow. - * @param other - * @return - */ - constexpr BigDecimal &operator+=(const BigDecimal &other) noexcept { - *this = *this + other; - return *this; - } + /** + * addition of two BigDecimals. + * may overflow. + * @param other + * @return + */ + constexpr BigDecimal &operator+=(BigDecimal const &other) noexcept { + *this = *this + other; + return *this; + } - /** - * subtraction of two BigDecimals. - * may overflow. - * @param other - * @return - */ - [[nodiscard]] constexpr BigDecimal operator-(const BigDecimal &other) const noexcept { - BigDecimal res{0}; - add_or_sub>(other, res); - return res; - } + /** + * subtraction of two BigDecimals. + * may overflow. + * @param other + * @return + */ + [[nodiscard]] constexpr BigDecimal operator-(BigDecimal const &other) const noexcept { + BigDecimal res{0}; + add_or_sub>(other, res); + return res; + } - /** - * subtraction of two BigDecimals. - * checks overflow. - * @param other - * @return - */ - [[nodiscard]] constexpr nonstd::expected sub_checked(const BigDecimal &other) const noexcept { - BigDecimal res{0}; - if (add_or_sub>(other, res)) - return nonstd::make_unexpected(DecimalError::Overflow); - return res; - } + /** + * subtraction of two BigDecimals. + * checks overflow. + * @param other + * @return + */ + [[nodiscard]] constexpr nonstd::expected sub_checked(BigDecimal const &other) const noexcept { + BigDecimal res{0}; + if (add_or_sub>(other, res)) + return nonstd::make_unexpected(DecimalError::Overflow); + return res; + } - /** - * subtraction of two BigDecimals. - * may overflow. - * @param other - * @return - */ - constexpr BigDecimal operator-=(const BigDecimal &other) noexcept { - *this = *this - other; - return *this; - } + /** + * subtraction of two BigDecimals. + * may overflow. + * @param other + * @return + */ + constexpr BigDecimal operator-=(BigDecimal const &other) noexcept { + *this = *this - other; + return *this; + } - /** - * multiplication of two BigDecimals. - * may overflow. - * @param other - * @return - */ - [[nodiscard]] constexpr BigDecimal operator*(const BigDecimal &other) const noexcept { - BigDecimal res{0}; - mul(other, res); - return res; - } + /** + * multiplication of two BigDecimals. + * may overflow. + * @param other + * @return + */ + [[nodiscard]] constexpr BigDecimal operator*(BigDecimal const &other) const noexcept { + BigDecimal res{0}; + mul(other, res); + return res; + } - /** - * multiplication of two BigDecimals. - * checks overflow. - * @param other - * @return - */ - [[nodiscard]] constexpr nonstd::expected mul_checked(const BigDecimal &other) const noexcept { - BigDecimal res{0}; - if (mul(other, res)) - return nonstd::make_unexpected(DecimalError::Overflow); - return res; - } + /** + * multiplication of two BigDecimals. + * checks overflow. + * @param other + * @return + */ + [[nodiscard]] constexpr nonstd::expected mul_checked(BigDecimal const &other) const noexcept { + BigDecimal res{0}; + if (mul(other, res)) + return nonstd::make_unexpected(DecimalError::Overflow); + return res; + } - /** - * multiplication of two BigDecimals. - * may overflow. - * @param other - * @return - */ - constexpr BigDecimal &operator*=(const BigDecimal &other) noexcept { - *this = *this * other; - return *this; - } + /** + * multiplication of two BigDecimals. + * may overflow. + * @param other + * @return + */ + constexpr BigDecimal &operator*=(BigDecimal const &other) noexcept { + *this = *this * other; + return *this; + } - /** - * division of two BigDecimals. - * may overflow. - * after 1000 decimal places have been added, stops dividing and floor. - * dividing by 0 is undefined behavior. - * @param other - * @return - */ - [[nodiscard]] constexpr BigDecimal operator/(const BigDecimal &other) const noexcept { - return div(other, 1000); - } + /** + * default value for scale increase when using operators. + * when this many decimal places have been added, stop dividing and floor. + */ + static constexpr Exponent_t default_max_scale_increase = 20; + + /** + * division of two BigDecimals. + * may overflow. + * after default_max_scale_increase decimal places have been added, stops dividing and floor. + * dividing by 0 is undefined behavior. + * @param other + * @return + */ + [[nodiscard]] constexpr BigDecimal operator/(BigDecimal const &other) const noexcept { + return div(other, default_max_scale_increase); + } - /** - * division of two BigDecimals. - * may overflow. - * dividing by 0 is undefined behavior. - * @param other - * @param max_scale_increase after this many decimal places have been added, stop dividing and round - * @param mode rounding mode to use - * @return - */ - [[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(other, max_scale_increase, mode, res); - return res; - } + /** + * division of two BigDecimals. + * may overflow. + * dividing by 0 is undefined behavior. + * @param other + * @param max_scale_increase after this many decimal places have been added, stop dividing and round + * @param mode rounding mode to use + * @return + */ + [[nodiscard]] constexpr BigDecimal div(BigDecimal const &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(other, max_scale_increase, mode, res); + return res; + } - /** - * division of two BigDecimals. - * checks overflow and division by 0. - * @param other - * @param max_scale_increase after this many decimal places have been added, stop dividing and round - * @param mode rounding mode to use - * @return - */ - [[nodiscard]] constexpr nonstd::expected 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(other, max_scale_increase, mode, res)) - return nonstd::make_unexpected(DecimalError::Overflow); - return res; - } + /** + * division of two BigDecimals. + * checks overflow and division by 0. + * @param other + * @param max_scale_increase after this many decimal places have been added, stop dividing and round + * @param mode rounding mode to use + * @return + */ + [[nodiscard]] constexpr nonstd::expected div_checked(BigDecimal const &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(other, max_scale_increase, mode, res)) + return nonstd::make_unexpected(DecimalError::Overflow); + return res; + } - /** - * division of two BigDecimals. - * may overflow. - * after 1000 decimal places have been added, stops dividing and floor. - * dividing by 0 is undefined behavior. - * @param other - * @return - */ - constexpr BigDecimal &operator/=(const BigDecimal &other) noexcept { - *this = *this / other; - return *this; - } + /** + * division of two BigDecimals. + * may overflow. + * after 1000 decimal places have been added, stops dividing and floor. + * dividing by 0 is undefined behavior. + * @param other + * @return + */ + constexpr BigDecimal &operator/=(BigDecimal const &other) noexcept { + *this = *this / other; + return *this; + } - /** - * raises a BigDecimal to the power of a int. - * may overflow. - * @param n - * @return - */ - [[nodiscard]] constexpr BigDecimal pow(unsigned int n) const noexcept { - BigDecimal r{0}; - pow(n, r); - return r; - } + /** + * raises a BigDecimal to the power of a int. + * may overflow. + * @param n + * @return + */ + [[nodiscard]] constexpr BigDecimal pow(unsigned int n) const noexcept { + BigDecimal r{0}; + pow(n, r); + return r; + } - /** - * raises a BigDecimal to the power of a int. - * checks overflow. - * @param n - * @return - */ - [[nodiscard]] constexpr nonstd::expected pow_checked(unsigned int n) const noexcept { - BigDecimal r{0}; - if (pow(n, r)) - return nonstd::make_unexpected(DecimalError::Overflow); - return r; - } + /** + * raises a BigDecimal to the power of a int. + * checks overflow. + * @param n + * @return + */ + [[nodiscard]] constexpr nonstd::expected pow_checked(unsigned int n) const noexcept { + BigDecimal r{0}; + if (pow(n, r)) + return nonstd::make_unexpected(DecimalError::Overflow); + return r; + } - /** - * rounds a BigDecimal with a specified RoundingMode. - * @param mode - * @return - */ - [[nodiscard]] constexpr BigDecimal round(RoundingMode mode) const noexcept { - if (exponent == 0) - return *this; - UnscaledValue_t v{base}; - if (pow_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); - } + /** + * rounds a BigDecimal with a specified RoundingMode. + * @param mode + * @return + */ + [[nodiscard]] constexpr BigDecimal round(RoundingMode mode) const noexcept { + if (exponent == 0) { + return *this; + } + UnscaledValue_t v{base}; + UnscaledValue_t uns = unscaled_value; + if (detail::pow_checked(v, exponent, v)) { + v = {base}; + uns /= 100; + if (detail::pow_checked(v, exponent - 2, v)) { + // base pow exponent overflows and this did not, we have to be close to 0 + return BigDecimal{0, 0}; + } + } + UnscaledValue_t rem = uns % v; + rem = rem / (v / base); // in rare cases, rem * 10 / v can overflow + v = uns / v; + return handle_rounding(v, 0, rem, mode); + } - /** - * the absolute value of a BigDecimal. - * may overflow. - * @return - */ - [[nodiscard]] constexpr BigDecimal abs() const noexcept { - if (positive()) - return *this; - else - return -*this; - } + /** + * the absolute value of a BigDecimal. + * may overflow. + * @return + */ + [[nodiscard]] constexpr BigDecimal abs() const noexcept { + if (positive()) + return *this; + else + return -*this; + } - /** - * the absolute value of a BigDecimal. - * checks overflow. - * @return - */ - [[nodiscard]] constexpr nonstd::expected abs_checked() const noexcept { - if (positive()) - return *this; - else - return this->unary_minus_checked(); - } + /** + * the absolute value of a BigDecimal. + * checks overflow. + * @return + */ + [[nodiscard]] constexpr nonstd::expected abs_checked() const noexcept { + if (positive()) + return *this; + else + return this->unary_minus_checked(); + } - /** - * comparison between BigDecimals. - * @param other - * @return - */ - 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(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(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(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(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; - }; - /** - * equality between BigDecimals. - * @param other - * @return - */ - constexpr bool operator==(const BigDecimal &other) const noexcept { - return *this <=> other == std::strong_ordering::equivalent; - } - /** - * equality between a BigDecimal and a int (mainly for constants) - * @param t - * @param other - * @return - */ - friend bool operator==(const BigDecimal &t, int other) noexcept { - return t == BigDecimal{other, 0}; - } - /** - * equality between a BigDecimal and a int (mainly for constants) - * @param t - * @param other - * @return - */ - friend bool operator==(int t, const BigDecimal &other) noexcept { - return other == t; - } + /** + * comparison between BigDecimals. + * @param other + * @return + */ + constexpr std::strong_ordering operator<=>(BigDecimal const &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; + // scale both values to have the same exponent + // if one of the decimals is 0, skip and compare directly (0*base^e is 0 for all e) + if (t != 0 && o != 0) { + if (this->exponent > other.exponent) { + UnscaledValue_t b{base}; + if (detail::pow_checked(b, this->exponent - other.exponent, b)) + return std::strong_ordering::less; // t does fit into the same precision, while o does not + if (detail::mul_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 (detail::pow_checked(b, other.exponent - this->exponent, b)) + return std::strong_ordering::greater; // o does fit into the same precision, while t does not + if (detail::mul_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; + }; + /** + * equality between BigDecimals. + * @param other + * @return + */ + constexpr bool operator==(BigDecimal const &other) const noexcept { + return (*this <=> other) == std::strong_ordering::equivalent; + } + /** + * equality between a BigDecimal and a int (mainly for constants) + * @param t + * @param other + * @return + */ + friend bool operator==(BigDecimal const &t, int other) noexcept { + return t == BigDecimal{other, 0}; + } + /** + * equality between a BigDecimal and a int (mainly for constants) + * @param t + * @param other + * @return + */ + friend bool operator==(int t, BigDecimal const &other) noexcept { + return other == t; + } - /** - * conversion to a double - * @return - */ - [[nodiscard]] explicit operator double() const noexcept { - double const v = cast_unscaled_to_double_safe(unscaled_value) * std::pow(static_cast(base), -static_cast(exponent)); - if (!std::isnan(v) && !std::isinf(v)) - return v; - // even Javas BigDecimal has no better solution - auto const s = static_cast(*this); - return std::stod(s); - } + /** + * conversion to a double + * @return + */ + [[nodiscard]] explicit operator double() const noexcept { + double const v = cast_unscaled_to_double_safe(unscaled_value) * std::pow(static_cast(base), -static_cast(exponent)); + if (!std::isnan(v) && !std::isinf(v)) + return v; + // even Javas BigDecimal has no better solution + auto const s = static_cast(*this); + return std::stod(s); + } - /** - * conversion to a float - * @return - */ - [[nodiscard]] explicit operator float() const noexcept { - return static_cast(static_cast(*this)); - } + /** + * conversion to a float + * @return + */ + [[nodiscard]] explicit operator float() const noexcept { + return static_cast(static_cast(*this)); + } - /** - * conversion to a UnscaledValue_t - * @return - */ - [[nodiscard]] constexpr explicit operator UnscaledValue_t() const noexcept { - if (exponent == 0) - return unscaled_value; - return round(RoundingMode::Trunc).unscaled_value; - } + /** + * conversion to a UnscaledValue_t + * @return + */ + [[nodiscard]] constexpr explicit operator UnscaledValue_t() const noexcept { + if (exponent == 0) + return unscaled_value; + return round(RoundingMode::Trunc).unscaled_value; + } - /** - * conversion to a string - * @return - */ - [[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(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()}; - } + /** + * conversion to a string + * @return + */ + [[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(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()}; + } - /** - * writing a BigDecimal into a stream. - * @param str - * @param bn - * @return - */ - friend std::ostream &operator<<(std::ostream &str, const BigDecimal &bn) { - auto s = static_cast(bn); - str << s; - return str; - } + /** + * writing a BigDecimal into a stream. + * @param str + * @param bn + * @return + */ + friend std::ostream &operator<<(std::ostream &str, BigDecimal const &bn) { + auto s = static_cast(bn); + str << s; + return str; + } - /** - * combined hash of a BigDecimals components hashes. - * @return - */ - template - [[nodiscard]] size_t hash() const { - BigDecimal n = *this; - n.normalize(); + /** + * combined hash of a BigDecimals components hashes. + * @return + */ + template + [[nodiscard]] size_t hash() const { + BigDecimal n = *this; + n.normalize(); - return dice::hash::dice_hash_templates::dice_hash(std::tie(n.unscaled_value, n.exponent)); - } -}; + return dice::hash::dice_hash_templates::dice_hash(std::tie(n.unscaled_value, n.exponent)); + } + }; -template -std::string to_string(const BigDecimal &r) noexcept { - return static_cast(r); -} + template + std::string to_string(BigDecimal const &r) noexcept { + return static_cast(r); + } -template -BigDecimal pow(const BigDecimal &r, unsigned int n) noexcept { - return r.pow(n); -} -template -BigDecimal round(const BigDecimal &r) noexcept { - return r.round(RoundingMode::Round); -} -template -BigDecimal floor(const BigDecimal &r) noexcept { - return r.round(RoundingMode::Floor); -} -template -BigDecimal ceil(const BigDecimal &r) noexcept { - return r.round(RoundingMode::Ceil); -} -template -BigDecimal trunc(const BigDecimal &r) noexcept { - return r.round(RoundingMode::Trunc); -} -template -BigDecimal abs(const BigDecimal &r) noexcept { - return r.abs(); -} + template + BigDecimal pow(BigDecimal const &r, unsigned int n) noexcept { + return r.pow(n); + } + template + BigDecimal round(BigDecimal const &r) noexcept { + return r.round(RoundingMode::Round); + } + template + BigDecimal floor(BigDecimal const &r) noexcept { + return r.round(RoundingMode::Floor); + } + template + BigDecimal ceil(BigDecimal const &r) noexcept { + return r.round(RoundingMode::Ceil); + } + template + BigDecimal trunc(BigDecimal const &r) noexcept { + return r.round(RoundingMode::Trunc); + } + template + BigDecimal abs(BigDecimal const &r) noexcept { + return r.abs(); + } + } // namespace util + + using Decimal128 = util::BigDecimal<>; } // namespace rdf4cpp #ifndef DOXYGEN_PARSER template -struct std::hash> { - size_t operator()(const rdf4cpp::BigDecimal &r) const { +struct std::hash> { + size_t operator()(rdf4cpp::util::BigDecimal const &r) const { return r.hash(); } }; @@ -912,19 +912,25 @@ struct dice::hash::dice_hash_overload return dice::hash::dice_hash_templates::dice_hash(std::hash<::boost::multiprecision::cpp_int>{}(x)); } }; +template +struct dice::hash::dice_hash_overload { + static size_t dice_hash(::boost::multiprecision::checked_int128_t const &x) noexcept { + return dice::hash::dice_hash_templates::dice_hash(std::hash<::boost::multiprecision::checked_int128_t>{}(x)); + } +}; template -struct dice::hash::dice_hash_overload> { - static size_t dice_hash(rdf4cpp::BigDecimal const &x) noexcept { +struct dice::hash::dice_hash_overload> { + static size_t dice_hash(rdf4cpp::util::BigDecimal const &x) noexcept { return x.template hash(); } }; #endif template -class std::numeric_limits> { +class std::numeric_limits> { public: - using BigDecimal = rdf4cpp::BigDecimal; + using BigDecimal = rdf4cpp::util::BigDecimal; static constexpr bool is_specialized = true; static constexpr bool is_signed = true; diff --git a/src/rdf4cpp/Literal.cpp b/src/rdf4cpp/Literal.cpp index 1a2ee88a0..30e61c8a1 100644 --- a/src/rdf4cpp/Literal.cpp +++ b/src/rdf4cpp/Literal.cpp @@ -2522,7 +2522,7 @@ Literal Literal::as_seconds(storage::DynNodeStoragePtr node_storage) const { auto r = this->seconds(); if (!r.has_value()) return Literal{}; - return Literal::make_typed_from_value(rdf4cpp::BigDecimal<>{r->count(), 9}, select_node_storage(node_storage)); + return Literal::make_typed_from_value(Decimal128{r->count(), 9}, select_node_storage(node_storage)); } std::optional Literal::timezone() const noexcept { diff --git a/src/rdf4cpp/Timezone.hpp b/src/rdf4cpp/Timezone.hpp index e59c45f37..6e786f5e0 100644 --- a/src/rdf4cpp/Timezone.hpp +++ b/src/rdf4cpp/Timezone.hpp @@ -485,12 +485,6 @@ struct dice::hash::dice_hash_overload { } }; template -struct dice::hash::dice_hash_overload { - static size_t dice_hash(::boost::multiprecision::cpp_int const &x) noexcept { - return dice::hash::dice_hash_templates::dice_hash(::boost::multiprecision::hash_value(x)); - } -}; -template struct dice::hash::dice_hash_overload { static size_t dice_hash(rdf4cpp::Year const &x) noexcept { return dice::hash::dice_hash_templates::dice_hash(static_cast(x)); diff --git a/src/rdf4cpp/datatypes/registry/util/CharConvExt.hpp b/src/rdf4cpp/datatypes/registry/util/CharConvExt.hpp index 3869d74a5..896b89450 100644 --- a/src/rdf4cpp/datatypes/registry/util/CharConvExt.hpp +++ b/src/rdf4cpp/datatypes/registry/util/CharConvExt.hpp @@ -10,6 +10,7 @@ #include #include +#include #include namespace rdf4cpp::datatypes::registry::util { @@ -88,6 +89,77 @@ F from_chars(std::string_view s) { return value; } +template +F from_chars(std::string_view s) { + if (s.starts_with('+')) { + s.remove_prefix(1); + } + if (auto const pos = s.find_first_not_of('0'); pos != std::string::npos) { + s.remove_prefix(pos); + } + + try { + return F{s}; + } + catch (std::runtime_error const &e) { + throw InvalidNode{std::format("{} parsing error: {}", datatype, e.what())}; + } +} + +#ifdef __SIZEOF_INT128__ +template +requires std::same_as +F from_chars(std::string_view s) { + static constexpr __int128 max_pow10 = []() { + auto n = std::numeric_limits::digits10; + __int128 d = 1; + for (int i = 0; i < n; ++i) { + d = d * 10; + } + return d; + }(); + static_assert(max_pow10 == 10'000'000'000'000'000'000ull); + + bool neg = false; + if (s.starts_with('+')) { + s.remove_prefix(1); + } + else if(s.starts_with('-')) { + neg = true; + s.remove_prefix(1); + } + + __int128 value = 0; + int i = 0; + while (!s.empty()) { + std::string_view p; + if (s.size() <= std::numeric_limits::digits10) { + p = s; + s = ""; + } + else { + const size_t pos = s.size() - std::numeric_limits::digits10; + p = s.substr(pos); + s = s.substr(0, pos); + } + __int128 value2 = from_chars(p); + if (neg) { + value2 = -value2; + } + for (int j = 0; j < i; ++j) { + if (rdf4cpp::util::detail::mul_checked(value2, max_pow10, value2)) [[unlikely]] { + throw rdf4cpp::InvalidNode{std::format("{} parsing error: overflow", datatype)}; + } + } + if (rdf4cpp::util::detail::add_checked(value, value2, value)) [[unlikely]] { + throw rdf4cpp::InvalidNode{std::format("{} parsing error: overflow", datatype)}; + } + ++i; + } + return value; +} +#endif + namespace detail { /** * equivalent to static_cast(1 + std::log10(value)) diff --git a/src/rdf4cpp/datatypes/xsd/Decimal.cpp b/src/rdf4cpp/datatypes/xsd/Decimal.cpp index 9a6f0ca96..a01485846 100644 --- a/src/rdf4cpp/datatypes/xsd/Decimal.cpp +++ b/src/rdf4cpp/datatypes/xsd/Decimal.cpp @@ -31,8 +31,7 @@ bool capabilities::Default::serialize_simplified_string(cpp_type co cpp_type v = value; v.normalize(); if (v.get_exponent() == 0) { - auto const s = static_cast(v).str(); - return writer::write_str(s, writer); + return rdf4cpp::util::to_chars_canonical(v.get_unscaled_value(), writer); } else { auto const s = static_cast(v); return writer::write_str(s, writer); @@ -71,7 +70,7 @@ nonstd::expected::div_result_cpp_type, Dynami return nonstd::make_unexpected(DynamicError::DivideByZero); } - auto r = lhs.div_checked(rhs, 1000); + auto r = lhs.div_checked(rhs, cpp_type::default_max_scale_increase); if (r.has_value()) return r.value(); else @@ -110,17 +109,17 @@ nonstd::expected::abs_result_cpp_type, Dynami template<> nonstd::expected::round_result_cpp_type, DynamicError> capabilities::Numeric::round(cpp_type const &operand) noexcept { - return operand.round(rdf4cpp::RoundingMode::Round); + return operand.round(rdf4cpp::util::RoundingMode::Round); } template<> nonstd::expected::floor_result_cpp_type, DynamicError> capabilities::Numeric::floor(cpp_type const &operand) noexcept { - return operand.round(rdf4cpp::RoundingMode::Floor); + return operand.round(rdf4cpp::util::RoundingMode::Floor); } template<> nonstd::expected::ceil_result_cpp_type, DynamicError> capabilities::Numeric::ceil(cpp_type const &operand) noexcept { - return operand.round(rdf4cpp::RoundingMode::Ceil); + return operand.round(rdf4cpp::util::RoundingMode::Ceil); } template<> @@ -150,8 +149,8 @@ std::optional capabilities::Inlineable(big_unscaled_value); - if (big_unscaled_value != unscaled_value) { + int64_t unscaled_value; + if (rdf4cpp::util::detail::cast_checked(big_unscaled_value, unscaled_value)) { // unscaled value > 64 bit, cannot fit return std::nullopt; } diff --git a/src/rdf4cpp/datatypes/xsd/Decimal.hpp b/src/rdf4cpp/datatypes/xsd/Decimal.hpp index bb6d93272..7bbe66c69 100644 --- a/src/rdf4cpp/datatypes/xsd/Decimal.hpp +++ b/src/rdf4cpp/datatypes/xsd/Decimal.hpp @@ -18,7 +18,7 @@ template<> struct DatatypeMapping { // needs at least 18 decimal digits of precision // see: https://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#dt-decimal - using cpp_datatype = rdf4cpp::BigDecimal<>; + using cpp_datatype = rdf4cpp::Decimal128; }; template<> diff --git a/src/rdf4cpp/datatypes/xsd/integers/non_negative/NonNegativeInteger.cpp b/src/rdf4cpp/datatypes/xsd/integers/non_negative/NonNegativeInteger.cpp index 3f28c16b4..a185b5bec 100644 --- a/src/rdf4cpp/datatypes/xsd/integers/non_negative/NonNegativeInteger.cpp +++ b/src/rdf4cpp/datatypes/xsd/integers/non_negative/NonNegativeInteger.cpp @@ -3,19 +3,14 @@ #include #include +#include namespace rdf4cpp::datatypes::registry { #ifndef DOXYGEN_PARSER template<> capabilities::Default::cpp_type capabilities::Default::from_string(std::string_view s) { - cpp_type ret; - - try { - ret = cpp_type{s}; - } catch (std::runtime_error const &e) { - throw InvalidNode{std::format("{} parsing error: {}", identifier, e.what())}; - } + cpp_type ret = util::from_chars(s); if (ret < 0) { throw InvalidNode{std::format("{} parsing error: found negative value", identifier)}; @@ -24,6 +19,11 @@ capabilities::Default::cpp_type capabilities::Default< return ret; } +template<> +bool capabilities::Default::serialize_canonical_string(cpp_type const &value, writer::BufWriterParts writer) noexcept { + return rdf4cpp::util::to_chars_canonical(value, writer); +} + template<> bool capabilities::Logical::effective_boolean_value(cpp_type const &value) noexcept { return value != 0; diff --git a/src/rdf4cpp/datatypes/xsd/integers/non_negative/NonNegativeInteger.hpp b/src/rdf4cpp/datatypes/xsd/integers/non_negative/NonNegativeInteger.hpp index 7583d2dc4..486f1b517 100644 --- a/src/rdf4cpp/datatypes/xsd/integers/non_negative/NonNegativeInteger.hpp +++ b/src/rdf4cpp/datatypes/xsd/integers/non_negative/NonNegativeInteger.hpp @@ -5,14 +5,14 @@ #include #include -#include +#include namespace rdf4cpp::datatypes::registry { #ifndef DOXYGEN_PARSER template<> struct DatatypeMapping { - using cpp_datatype = boost::multiprecision::cpp_int; + using cpp_datatype = rdf4cpp::Int128; }; template<> @@ -28,6 +28,9 @@ struct DatatypeNumericStubMapping { template<> capabilities::Default::cpp_type capabilities::Default::from_string(std::string_view s); +template<> +bool capabilities::Default::serialize_canonical_string(cpp_type const &value, writer::BufWriterParts writer) noexcept; + template<> bool capabilities::Logical::effective_boolean_value(cpp_type const &value) noexcept; diff --git a/src/rdf4cpp/datatypes/xsd/integers/non_negative/PositiveInteger.cpp b/src/rdf4cpp/datatypes/xsd/integers/non_negative/PositiveInteger.cpp index 1eba6d88e..5413e89bb 100644 --- a/src/rdf4cpp/datatypes/xsd/integers/non_negative/PositiveInteger.cpp +++ b/src/rdf4cpp/datatypes/xsd/integers/non_negative/PositiveInteger.cpp @@ -1,19 +1,14 @@ #include "PositiveInteger.hpp" #include +#include namespace rdf4cpp::datatypes::registry { #ifndef DOXYGEN_PARSER template<> capabilities::Default::cpp_type capabilities::Default::from_string(std::string_view s) { - cpp_type ret; - - try { - ret = cpp_type{s}; - } catch (std::runtime_error const &e) { - throw InvalidNode{std::format("{} parsing error: {}", identifier, e.what())}; - } + cpp_type ret = util::from_chars(s); if (ret < 1) { throw InvalidNode{std::format("{} parsing error: found non-positive value", identifier)}; @@ -22,6 +17,11 @@ capabilities::Default::cpp_type capabilities::Default +bool capabilities::Default::serialize_canonical_string(cpp_type const &value, writer::BufWriterParts writer) noexcept { + return rdf4cpp::util::to_chars_canonical(value, writer); +} + template<> bool capabilities::Logical::effective_boolean_value(cpp_type const &) noexcept { return true; diff --git a/src/rdf4cpp/datatypes/xsd/integers/non_negative/PositiveInteger.hpp b/src/rdf4cpp/datatypes/xsd/integers/non_negative/PositiveInteger.hpp index 83bc5f082..ca3b84482 100644 --- a/src/rdf4cpp/datatypes/xsd/integers/non_negative/PositiveInteger.hpp +++ b/src/rdf4cpp/datatypes/xsd/integers/non_negative/PositiveInteger.hpp @@ -6,14 +6,14 @@ #include #include -#include +#include namespace rdf4cpp::datatypes::registry { #ifndef DOXYGEN_PARSER template<> struct DatatypeMapping { - using cpp_datatype = boost::multiprecision::cpp_int; + using cpp_datatype = rdf4cpp::Int128; }; template<> @@ -29,6 +29,9 @@ struct DatatypeNumericStubMapping { template<> capabilities::Default::cpp_type capabilities::Default::from_string(std::string_view s); +template<> +bool capabilities::Default::serialize_canonical_string(cpp_type const &value, writer::BufWriterParts writer) noexcept; + template<> bool capabilities::Logical::effective_boolean_value(cpp_type const &) noexcept; diff --git a/src/rdf4cpp/datatypes/xsd/integers/non_positive/NegativeInteger.cpp b/src/rdf4cpp/datatypes/xsd/integers/non_positive/NegativeInteger.cpp index ca884395e..a0ee75c32 100644 --- a/src/rdf4cpp/datatypes/xsd/integers/non_positive/NegativeInteger.cpp +++ b/src/rdf4cpp/datatypes/xsd/integers/non_positive/NegativeInteger.cpp @@ -3,19 +3,14 @@ #include #include +#include namespace rdf4cpp::datatypes::registry { #ifndef DOXYGEN_PARSER template<> capabilities::Default::cpp_type capabilities::Default::from_string(std::string_view s) { - cpp_type ret; - - try { - ret = cpp_type{s}; - } catch (std::runtime_error const &e) { - throw InvalidNode{std::format("{} parsing error: {}", identifier, e.what())}; - } + cpp_type ret = util::from_chars(s); if (ret > -1) { throw InvalidNode{std::format("{} parsing error: found non-negative value", identifier)}; @@ -24,6 +19,11 @@ capabilities::Default::cpp_type capabilities::Default +bool capabilities::Default::serialize_canonical_string(cpp_type const &value, writer::BufWriterParts writer) noexcept { + return rdf4cpp::util::to_chars_canonical(value, writer); +} + template<> bool capabilities::Logical::effective_boolean_value(cpp_type const &) noexcept { return true; diff --git a/src/rdf4cpp/datatypes/xsd/integers/non_positive/NegativeInteger.hpp b/src/rdf4cpp/datatypes/xsd/integers/non_positive/NegativeInteger.hpp index dc53f6ec7..5044bb9bd 100644 --- a/src/rdf4cpp/datatypes/xsd/integers/non_positive/NegativeInteger.hpp +++ b/src/rdf4cpp/datatypes/xsd/integers/non_positive/NegativeInteger.hpp @@ -6,14 +6,14 @@ #include #include -#include +#include namespace rdf4cpp::datatypes::registry { #ifndef DOXYGEN_PARSER template<> struct DatatypeMapping { - using cpp_datatype = boost::multiprecision::cpp_int; + using cpp_datatype = rdf4cpp::Int128; }; template<> @@ -32,6 +32,9 @@ struct DatatypeNumericStubMapping { template<> capabilities::Default::cpp_type capabilities::Default::from_string(std::string_view s); +template<> +bool capabilities::Default::serialize_canonical_string(cpp_type const &value, writer::BufWriterParts writer) noexcept; + template<> bool capabilities::Logical::effective_boolean_value(cpp_type const &) noexcept; diff --git a/src/rdf4cpp/datatypes/xsd/integers/non_positive/NonPositiveInteger.cpp b/src/rdf4cpp/datatypes/xsd/integers/non_positive/NonPositiveInteger.cpp index 04477339b..292dc0796 100644 --- a/src/rdf4cpp/datatypes/xsd/integers/non_positive/NonPositiveInteger.cpp +++ b/src/rdf4cpp/datatypes/xsd/integers/non_positive/NonPositiveInteger.cpp @@ -3,19 +3,14 @@ #include #include +#include namespace rdf4cpp::datatypes::registry { #ifndef DOXYGEN_PARSER template<> capabilities::Default::cpp_type capabilities::Default::from_string(std::string_view s) { - cpp_type ret; - - try { - ret = cpp_type{s}; - } catch (std::runtime_error const &e) { - throw InvalidNode{std::format("{} parsing error: {}",identifier, e.what())}; - } + cpp_type ret = util::from_chars(s); if (ret > 0) { throw InvalidNode{std::format("{} parsing error: found non-negative value", identifier)}; @@ -24,6 +19,11 @@ capabilities::Default::cpp_type capabilities::Default< return ret; } +template<> +bool capabilities::Default::serialize_canonical_string(cpp_type const &value, writer::BufWriterParts writer) noexcept { + return rdf4cpp::util::to_chars_canonical(value, writer); +} + template<> bool capabilities::Logical::effective_boolean_value(cpp_type const &value) noexcept { return value != 0; diff --git a/src/rdf4cpp/datatypes/xsd/integers/non_positive/NonPositiveInteger.hpp b/src/rdf4cpp/datatypes/xsd/integers/non_positive/NonPositiveInteger.hpp index 94035e91c..998feaabe 100644 --- a/src/rdf4cpp/datatypes/xsd/integers/non_positive/NonPositiveInteger.hpp +++ b/src/rdf4cpp/datatypes/xsd/integers/non_positive/NonPositiveInteger.hpp @@ -6,14 +6,14 @@ #include #include -#include +#include namespace rdf4cpp::datatypes::registry { #ifndef DOXYGEN_PARSER template<> struct DatatypeMapping { - using cpp_datatype = boost::multiprecision::cpp_int; + using cpp_datatype = rdf4cpp::Int128; }; template<> @@ -29,6 +29,9 @@ struct DatatypeNumericStubMapping { template<> capabilities::Default::cpp_type capabilities::Default::from_string(std::string_view s); +template<> +bool capabilities::Default::serialize_canonical_string(cpp_type const &value, writer::BufWriterParts writer) noexcept; + template<> bool capabilities::Logical::effective_boolean_value(cpp_type const &value) noexcept; diff --git a/src/rdf4cpp/datatypes/xsd/integers/signed/Integer.cpp b/src/rdf4cpp/datatypes/xsd/integers/signed/Integer.cpp index d20e956d7..364f2ef8c 100644 --- a/src/rdf4cpp/datatypes/xsd/integers/signed/Integer.cpp +++ b/src/rdf4cpp/datatypes/xsd/integers/signed/Integer.cpp @@ -3,25 +3,19 @@ #include #include +#include namespace rdf4cpp::datatypes::registry { #ifndef DOXYGEN_PARSER template<> capabilities::Default::cpp_type capabilities::Default::from_string(std::string_view s) { - if (s.starts_with('+')) { - s.remove_prefix(1); - } - - if (auto const pos = s.find_first_not_of('0'); pos != std::string::npos) { - s.remove_prefix(pos); - } + return util::from_chars(s); +} - try { - return cpp_type{s}; - } catch (std::runtime_error const &e) { - throw InvalidNode{std::format("{} parsing error: {}", identifier, e.what())}; - } +template<> +bool capabilities::Default::serialize_canonical_string(cpp_type const &value, writer::BufWriterParts writer) noexcept { + return rdf4cpp::util::to_chars_canonical(value, writer); } template<> @@ -29,6 +23,28 @@ bool capabilities::Logical::effective_boolean_value(cpp_type const return value != 0; } +template<> +nonstd::expected::add_result_cpp_type, DynamicError> capabilities::Numeric::add(cpp_type const &lhs, cpp_type const &rhs) noexcept { + // https://www.w3.org/TR/xpath-functions/#op.numeric + // needs overflow protection + cpp_type r; + if (rdf4cpp::util::detail::add_checked(lhs, rhs, r)) { + return nonstd::make_unexpected(DynamicError::OverOrUnderFlow); + } + return r; +} + +template<> +nonstd::expected::sub_result_cpp_type, DynamicError> capabilities::Numeric::sub(cpp_type const &lhs, cpp_type const &rhs) noexcept { + // https://www.w3.org/TR/xpath-functions/#op.numeric + // needs overflow protection + cpp_type r; + if (rdf4cpp::util::detail::sub_checked(lhs, rhs, r)) { + return nonstd::make_unexpected(DynamicError::OverOrUnderFlow); + } + return r; +} + template<> nonstd::expected::div_result_cpp_type, DynamicError> capabilities::Numeric::div(cpp_type const &lhs, cpp_type const &rhs) noexcept { if (rhs == 0) { @@ -40,9 +56,33 @@ nonstd::expected::div_result_cpp_type, Dynami return static_cast(lhs) / static_cast(rhs); } +template<> +nonstd::expected::mul_result_cpp_type, DynamicError> capabilities::Numeric::mul(cpp_type const &lhs, cpp_type const &rhs) noexcept { + // https://www.w3.org/TR/xpath-functions/#op.numeric + // decimal needs overflow protection + cpp_type r; + if (rdf4cpp::util::detail::mul_checked(lhs, rhs, r)) { + return nonstd::make_unexpected(DynamicError::OverOrUnderFlow); + } + return r; +} + +template<> +nonstd::expected::abs_result_cpp_type, DynamicError> capabilities::Numeric::neg(cpp_type const &operand) noexcept { + cpp_type r; + if (rdf4cpp::util::detail::mul_checked(operand, cpp_type{-1}, r)) { + return nonstd::make_unexpected(DynamicError::OverOrUnderFlow); + } + return r; +} + template<> nonstd::expected::abs_result_cpp_type, DynamicError> capabilities::Numeric::abs(cpp_type const &operand) noexcept { - return boost::multiprecision::abs(operand); + if (operand >= 0) { + return operand; + } else { + return neg(operand); + } } template<> diff --git a/src/rdf4cpp/datatypes/xsd/integers/signed/Integer.hpp b/src/rdf4cpp/datatypes/xsd/integers/signed/Integer.hpp index 16aeb470b..8f70b6d40 100644 --- a/src/rdf4cpp/datatypes/xsd/integers/signed/Integer.hpp +++ b/src/rdf4cpp/datatypes/xsd/integers/signed/Integer.hpp @@ -6,14 +6,14 @@ #include #include -#include +#include namespace rdf4cpp::datatypes::registry { #ifndef DOXYGEN_PARSER template<> struct DatatypeMapping { - using cpp_datatype = boost::multiprecision::cpp_int; + using cpp_datatype = rdf4cpp::Int128; }; template<> @@ -29,12 +29,27 @@ struct DatatypeDivResultMapping { template<> capabilities::Default::cpp_type capabilities::Default::from_string(std::string_view s); +template<> +bool capabilities::Default::serialize_canonical_string(cpp_type const &value, writer::BufWriterParts writer) noexcept; + template<> bool capabilities::Logical::effective_boolean_value(cpp_type const &value) noexcept; +template<> +nonstd::expected::add_result_cpp_type, DynamicError> capabilities::Numeric::add(cpp_type const &lhs, cpp_type const &rhs) noexcept; + +template<> +nonstd::expected::sub_result_cpp_type, DynamicError> capabilities::Numeric::sub(cpp_type const &lhs, cpp_type const &rhs) noexcept; + template<> nonstd::expected::div_result_cpp_type, DynamicError> capabilities::Numeric::div(cpp_type const &lhs, cpp_type const &rhs) noexcept; +template<> +nonstd::expected::mul_result_cpp_type, DynamicError> capabilities::Numeric::mul(cpp_type const &lhs, cpp_type const &rhs) noexcept; + +template<> +nonstd::expected::abs_result_cpp_type, DynamicError> capabilities::Numeric::neg(cpp_type const &operand) noexcept; + template<> nonstd::expected::abs_result_cpp_type, DynamicError> capabilities::Numeric::abs(cpp_type const &operand) noexcept; diff --git a/src/rdf4cpp/datatypes/xsd/time/DayTimeDuration.cpp b/src/rdf4cpp/datatypes/xsd/time/DayTimeDuration.cpp index 4d1cefe33..3bf02648e 100644 --- a/src/rdf4cpp/datatypes/xsd/time/DayTimeDuration.cpp +++ b/src/rdf4cpp/datatypes/xsd/time/DayTimeDuration.cpp @@ -170,7 +170,7 @@ capabilities::Duration::duration_sub(cpp_type const &lhs, c template<> nonstd::expected::duration_div_result_cpp_type, DynamicError> capabilities::Duration::duration_div(cpp_type const &lhs, cpp_type const &rhs) noexcept { - auto r = BigDecimal<>{lhs.count(), 0}.div_checked(BigDecimal<>{rhs.count(), 0}, 1000); + auto r = Decimal128{lhs.count(), 0}.div_checked(Decimal128{rhs.count(), 0}, 1000); if (!r.has_value()) { return nonstd::make_unexpected(DynamicError::DivideByZero); } diff --git a/src/rdf4cpp/datatypes/xsd/time/YearMonthDuration.cpp b/src/rdf4cpp/datatypes/xsd/time/YearMonthDuration.cpp index 1f97c3478..40d9b59b9 100644 --- a/src/rdf4cpp/datatypes/xsd/time/YearMonthDuration.cpp +++ b/src/rdf4cpp/datatypes/xsd/time/YearMonthDuration.cpp @@ -130,7 +130,7 @@ capabilities::Duration::duration_sub(cpp_type const &lhs, template<> nonstd::expected::duration_div_result_cpp_type, DynamicError> capabilities::Duration::duration_div(cpp_type const &lhs, cpp_type const &rhs) noexcept { - auto r = BigDecimal<>{lhs.count(), 0}.div_checked(BigDecimal<>{rhs.count(), 0}, 1000); + auto r = Decimal128{lhs.count(), 0}.div_checked(Decimal128{rhs.count(), 0}, 1000); if (!r.has_value()) { return nonstd::make_unexpected(DynamicError::DivideByZero); } diff --git a/src/rdf4cpp/util/Int128.hpp b/src/rdf4cpp/util/Int128.hpp new file mode 100644 index 000000000..c2c67a9e7 --- /dev/null +++ b/src/rdf4cpp/util/Int128.hpp @@ -0,0 +1,338 @@ +#ifndef RDF4CPP_INT128_HPP +#define RDF4CPP_INT128_HPP + +// checked arithmetic functions returning a boolean to indicate failure +// follow the example of https://gcc.gnu.org/onlinedocs/gcc/Integer-Overflow-Builtins.html +// and return true, iff the operation failed. + +#include +#include +#include + +namespace rdf4cpp { +#ifdef __SIZEOF_INT128__ + using Int128 = __int128; +#else + using Int128 = boost::multiprecision::checked_int128_t; +#endif + + namespace util { + // string -> int128 can be found in CharConvExt + +#ifdef __SIZEOF_INT128__ + inline std::to_chars_result to_chars(char *first, char *last, unsigned __int128 val) noexcept { + static constexpr unsigned __int128 max_pow10 = []() { + auto n = std::numeric_limits::digits10; + unsigned __int128 d = 1; + for (int i = 0; i < n; ++i) { + d = d * 10; + } + return d; + }(); + + if (val <= std::numeric_limits::max()) { + return std::to_chars(first, last, static_cast(val)); + } + + std::to_chars_result upper = to_chars(first, last, val / max_pow10); + if (upper.ec != std::errc{}) { + return upper; + } + + if (last - upper.ptr < std::numeric_limits::digits10) { + return { last, std::errc::value_too_large }; + } + + std::array::digits10+1> buff; + std::to_chars_result lower = std::to_chars(buff.data(), buff.data()+buff.size(), static_cast(val % max_pow10)); + + if (lower.ec != std::errc{}) { + return lower; + } + + const auto written = lower.ptr - buff.data(); + const auto n_zeroes = std::numeric_limits::digits10 - written; + + char* curr = upper.ptr; + RDF4CPP_DEBUG_ASSERT(last - curr >= n_zeroes); + std::memset(curr, '0', n_zeroes); + curr += n_zeroes; + RDF4CPP_DEBUG_ASSERT(last - curr >= written); + std::memcpy(curr, buff.data(), written); + curr += written; + + lower.ptr = curr; + + return lower; + } + inline std::to_chars_result to_chars(char *first, char *last, __int128 val) noexcept { + if (val >= 0) { + return to_chars(first, last, static_cast(val)); + } + + if (val == std::numeric_limits<__int128>::min()) [[unlikely]] { + static constexpr std::string_view data = "-170141183460469231731687303715884105728"; + static constexpr auto write = data; + if ((last - first) < static_cast(write.size())) { + return std::to_chars_result{.ptr = first, .ec = std::errc::value_too_large}; + } + std::memcpy(first, write.data(), write.size()); + return std::to_chars_result{.ptr = first + write.size(), .ec = std::errc{}}; + } + + if (first == last) [[unlikely]] { + return std::to_chars_result{.ptr = first, .ec = std::errc::value_too_large}; + } + + *first++ = '-'; + return to_chars(first, last, static_cast(-val)); + } + inline bool to_chars_canonical(__int128 const value, writer::BufWriterParts const writer) noexcept { + // +1 because of definition of digits10 https://en.cppreference.com/w/cpp/types/numeric_limits/digits10 + // +1 for sign + static constexpr size_t buf_sz = std::numeric_limits<__int128>::digits10 + 1 + 1; + + std::array buf; + std::to_chars_result const res = to_chars(buf.data(), buf.data() + buf.size(), value); + + RDF4CPP_ASSERT(res.ec == std::errc{}); + + std::string_view const s{buf.data(), static_cast(res.ptr - buf.data())}; + return writer::write_str(s, writer); + } +#endif + + namespace detail { + enum struct OverflowMode { + Checked, + UndefinedBehavior, + }; + + template + using cpp_int_checked = boost::multiprecision::number>; + template + using cpp_int_unchecked = boost::multiprecision::number>; + + template + concept IntegralExt = std::integral || std::same_as || std::same_as; + template + concept BoostNumber = std::same_as>; + + template + requires IntegralExt + static constexpr bool add_checked(T const &a, T const &b, T &result) noexcept { + if constexpr (m == OverflowMode::Checked) { + return __builtin_add_overflow(a, b, &result); + } else { + result = a + b; + return false; + } + } + template + static constexpr bool add_checked(cpp_int_checked const &a, + cpp_int_checked const &b, + cpp_int_checked &result) noexcept { + if (m == OverflowMode::Checked) { + try { + result = a + b; + } catch (std::overflow_error const &) { + return true; + } catch (std::range_error const &) { + return true; + } + return false; + } else { + using Unc = cpp_int_unchecked; + result = cpp_int_checked{Unc{a} + Unc{b}}; + return false; + } + } + + template + requires IntegralExt + static constexpr bool sub_checked(T const &a, T const &b, T &result) noexcept { + if constexpr (m == OverflowMode::Checked) { + return __builtin_sub_overflow(a, b, &result); + } else { + result = a - b; + return false; + } + } + template + static constexpr bool sub_checked(cpp_int_checked const &a, + cpp_int_checked const &b, + cpp_int_checked &result) noexcept { + if (m == OverflowMode::Checked) { + try { + result = a - b; + } catch (std::overflow_error const &) { + return true; + } catch (std::range_error const &) { + return true; + } + return false; + } else { + using Unc = cpp_int_unchecked; + result = cpp_int_checked{Unc{a} - Unc{b}}; + return false; + } + } + + template + requires IntegralExt + static constexpr bool mul_checked(T const &a, T const &b, T &result) noexcept { + if constexpr (m == OverflowMode::Checked) { + return __builtin_mul_overflow(a, b, &result); + } else { + result = a * b; + return false; + } + } + template + static constexpr bool mul_checked(cpp_int_checked const &a, + cpp_int_checked const &b, + cpp_int_checked &result) noexcept { + if (m == OverflowMode::Checked) { + try { + result = a * b; + } catch (std::overflow_error const &) { + return true; + } catch (std::range_error const &) { + return true; + } + return false; + } else { + using Unc = cpp_int_unchecked; + result = cpp_int_checked{Unc{a} * Unc{b}}; + return false; + } + } + + template + requires IntegralExt + static constexpr bool pow_checked(T const &a, unsigned int b, T &result) noexcept { + T r = 1; + bool over = false; + for (unsigned int i = 0; i < b; ++i) { + over |= mul_checked(r, a, r); + } + result = r; + return over; + } + template + static constexpr bool pow_checked(cpp_int_checked const &a, + unsigned int b, + cpp_int_checked &result) noexcept { + if (m == OverflowMode::Checked) { + try { + result = boost::multiprecision::pow(a, b); + } catch (std::overflow_error const &) { + return true; + } catch (std::range_error const &) { + return true; + } + return false; + } else { + using Unc = cpp_int_unchecked; + result = cpp_int_checked{boost::multiprecision::pow(Unc{a}, b)}; + return false; + } + } + + template + struct MakeUnsigned { + using t = std::make_unsigned_t; + }; + template<> + struct MakeUnsigned<__int128> { + using t = unsigned __int128; + }; + template + struct MakeUnsigned>> { + using t = boost::multiprecision::number>; + }; + template + requires IntegralExt && IntegralExt + static constexpr bool cast_checked(From const &f, To &result) noexcept { + if constexpr (m == OverflowMode::Checked) { + if constexpr (std::numeric_limits::is_signed == std::numeric_limits::is_signed) { + if (std::numeric_limits::min() > f || f > std::numeric_limits::max()) { + return true; + } + } else if constexpr (std::numeric_limits::is_signed) { + if (f < 0 || static_cast::t>(f) > std::numeric_limits::max()) { + return true; + } + } else { + if (f > std::numeric_limits::max()) { + return true; + } + } + } + result = static_cast(f); + return false; + } + template + static constexpr bool cast_checked(cpp_int_checked const &f, To &result) noexcept { + if constexpr (m == OverflowMode::Checked) { + try { + result = static_cast(f); + } catch (std::overflow_error const &) { + return true; + } catch (std::range_error const &) { + return true; + } + return false; + } + result = static_cast(static_cast>(f)); + return false; + } + template + static constexpr bool cast_checked(From const &f, cpp_int_checked &result) noexcept { + if constexpr (m == OverflowMode::Checked) { + try { + result = static_cast>(f); + } catch (std::overflow_error const &) { + return true; + } catch (std::range_error const &) { + return true; + } + return false; + } + result = static_cast>(static_cast>(f)); + return false; + } + } // namespace detail + + template + bool to_chars_canonical(T const &value, writer::BufWriterParts const writer) noexcept { + std::stringstream s{}; + s << value; + return writer::write_str(s.view(), writer); + } + } // namespace util +} // namespace rdf4cpp + +#ifdef __SIZEOF_INT128__ + +template +struct dice::hash::dice_hash_overload { + static size_t dice_hash(__int128 const &x) noexcept { + return dice::hash::dice_hash_templates::dice_hash(std::bit_cast>(x)); + } +}; +template +struct dice::hash::dice_hash_overload> { + static size_t dice_hash(boost::multiprecision::number const &x) noexcept { + return dice::hash::dice_hash_templates::dice_hash(::boost::multiprecision::hash_value(x)); + } +}; +inline std::ostream &operator<<(std::ostream &str, __int128 const &bn) { + rdf4cpp::writer::BufOStreamWriter w{str}; + rdf4cpp::util::to_chars_canonical(bn, w); + w.finalize(); + return str; +} +#endif + +#endif //RDF4CPP_INT128_HPP diff --git a/src/rdf4cpp/writer/BufWriter.hpp b/src/rdf4cpp/writer/BufWriter.hpp index 47cf98cb4..4768be6be 100644 --- a/src/rdf4cpp/writer/BufWriter.hpp +++ b/src/rdf4cpp/writer/BufWriter.hpp @@ -8,6 +8,7 @@ #include #include #include +#include namespace rdf4cpp::writer { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 548eb1d86..5c0694a87 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -282,6 +282,12 @@ target_link_libraries(bench_SerDe rdf4cpp ) +add_executable(bench_i128 bench_i128.cpp) +target_link_libraries(bench_i128 + nanobench::nanobench + rdf4cpp +) + add_executable(tests_RDFFileParser parser/tests_RDFFileParser.cpp) target_link_libraries(tests_RDFFileParser doctest::doctest diff --git a/tests/bench_i128.cpp b/tests/bench_i128.cpp new file mode 100644 index 000000000..eb8b8f877 --- /dev/null +++ b/tests/bench_i128.cpp @@ -0,0 +1,25 @@ +#define ANKERL_NANOBENCH_IMPLEMENT +#include +#include + +int main() { + __int128 k = 100; + boost::multiprecision::checked_int128_t v = 1000; + + ankerl::nanobench::Bench{}.relative(true) + .run("builtin", [&k]() { + for (int i=0;i<100;++i) { + __builtin_mul_overflow(k, k, &k); + } + }) + .run("boost", [&v]() { + for (int i=0;i<100;++i) { + try { + v = v * v; + } + catch (...) {} + } + }); + + std::cout << static_cast(k) << v; +} diff --git a/tests/datatype/tests_Decimal.cpp b/tests/datatype/tests_Decimal.cpp index 3a21bf796..81cdc6b70 100644 --- a/tests/datatype/tests_Decimal.cpp +++ b/tests/datatype/tests_Decimal.cpp @@ -18,6 +18,7 @@ TEST_CASE("decimal capabilities") { } TEST_CASE("Datatype Decimal") { + CHECK(storage::default_node_storage.has_specialized_storage_for(datatypes::xsd::Decimal::fixed_id)); constexpr auto correct_type_iri_cstr = "http://www.w3.org/2001/XMLSchema#decimal"; @@ -66,10 +67,6 @@ TEST_CASE("Datatype Decimal") { CHECK(lit8.value() == value); CHECK(lit8.lexical_form() == rdf_dbl_1_0); - value = type{std::numeric_limits::max()}; - auto lit9 = Literal::make_typed(to_string(value), type_iri); - CHECK(lit9.value() == value); - value = type{"3.111"}; auto lit10 = Literal::make_typed_from_value(value); CHECK(lit10.value() == value); @@ -185,7 +182,7 @@ TEST_CASE("decimal inlining sanity check") { SUBCASE("limits") { SUBCASE("unscaled value") { - boost::multiprecision::cpp_int const very_big_value{"99999999999999999999999999999999999999999999999"}; + auto const very_big_value = std::numeric_limits::max(); CHECK_GT(very_big_value, std::numeric_limits::max()); // way over the limit @@ -217,7 +214,7 @@ TEST_CASE("decimal inlining sanity check") { } SUBCASE("exponent") { - auto const l = Literal::make_typed_from_value(Decimal::cpp_type{boost::multiprecision::cpp_int{5}, 1U << 10}); + auto const l = Literal::make_typed_from_value(Decimal::cpp_type{rdf4cpp::Int128{5}, 1U << 10}); CHECK(!l.is_inlined()); CHECK(l.value() == Decimal::cpp_type(5, 1U << 10)); } diff --git a/tests/datatype/tests_Integer.cpp b/tests/datatype/tests_Integer.cpp index ac85bc0fe..0f89e0a8f 100644 --- a/tests/datatype/tests_Integer.cpp +++ b/tests/datatype/tests_Integer.cpp @@ -15,9 +15,11 @@ TEST_CASE("integer capabilities") { static_assert(datatypes::xsd::Integer::subtype_rank == 1); static_assert(datatypes::ComparableLiteralDatatype); static_assert(datatypes::FixedIdLiteralDatatype); + static_assert(std::numeric_limits::digits10 > 16); } TEST_CASE("Datatype Integer") { + CHECK(storage::default_node_storage.has_specialized_storage_for(datatypes::xsd::Integer::fixed_id)); constexpr auto correct_type_iri_cstr = "http://www.w3.org/2001/XMLSchema#integer"; @@ -73,7 +75,7 @@ TEST_CASE("Datatype Integer") { // suppress warnings regarding attribute ‘nodiscard’ Literal no_discard_dummy; - CHECK_THROWS_WITH_AS(no_discard_dummy = Literal::make_typed("a23dg", type_iri), "http://www.w3.org/2001/XMLSchema#integer parsing error: Unexpected character encountered in input.", InvalidNode); + CHECK_THROWS_WITH_AS(no_discard_dummy = Literal::make_typed("a23dg", type_iri), doctest::Contains("http://www.w3.org/2001/XMLSchema#integer parsing error: "), InvalidNode); CHECK_THROWS(no_discard_dummy = Literal::make_typed("2.2e-308", type_iri)); } @@ -99,7 +101,7 @@ TEST_CASE("integer inlining") { } SUBCASE("too large") { - auto lit = Literal::make_typed_from_value(xsd::Integer::cpp_type{"9999999999999999999999"}); + auto lit = Literal::make_typed("9999999999999999999999"); CHECK(!lit.backend_handle().is_inlined()); } } diff --git a/tests/datatype/tests_NegativeInteger.cpp b/tests/datatype/tests_NegativeInteger.cpp index cf4c9d3b8..ff961af43 100644 --- a/tests/datatype/tests_NegativeInteger.cpp +++ b/tests/datatype/tests_NegativeInteger.cpp @@ -6,6 +6,7 @@ using namespace rdf4cpp; TEST_CASE("Datatype NegativeInteger") { + CHECK(storage::default_node_storage.has_specialized_storage_for(datatypes::xsd::NegativeInteger::fixed_id)); constexpr auto correct_type_iri_cstr = "http://www.w3.org/2001/XMLSchema#negativeInteger"; diff --git a/tests/datatype/tests_NonNegativeInteger.cpp b/tests/datatype/tests_NonNegativeInteger.cpp index 7f0f40a6f..43d0230b8 100644 --- a/tests/datatype/tests_NonNegativeInteger.cpp +++ b/tests/datatype/tests_NonNegativeInteger.cpp @@ -6,6 +6,7 @@ using namespace rdf4cpp; TEST_CASE("Datatype NonNegativeInteger") { + CHECK(storage::default_node_storage.has_specialized_storage_for(datatypes::xsd::NonNegativeInteger::fixed_id)); constexpr auto correct_type_iri_cstr = "http://www.w3.org/2001/XMLSchema#nonNegativeInteger"; diff --git a/tests/datatype/tests_NonPositiveInteger.cpp b/tests/datatype/tests_NonPositiveInteger.cpp index 9ff49c944..596d73348 100644 --- a/tests/datatype/tests_NonPositiveInteger.cpp +++ b/tests/datatype/tests_NonPositiveInteger.cpp @@ -6,6 +6,7 @@ using namespace rdf4cpp; TEST_CASE("Datatype NonPositiveInteger") { + CHECK(storage::default_node_storage.has_specialized_storage_for(datatypes::xsd::NonPositiveInteger::fixed_id)); constexpr auto correct_type_iri_cstr = "http://www.w3.org/2001/XMLSchema#nonPositiveInteger"; diff --git a/tests/datatype/tests_NumOpResults.cpp b/tests/datatype/tests_NumOpResults.cpp index 3779653db..a6e8a4d40 100644 --- a/tests/datatype/tests_NumOpResults.cpp +++ b/tests/datatype/tests_NumOpResults.cpp @@ -26,7 +26,7 @@ TEST_SUITE("numeric op results") { Decimal::cpp_type const zero{0}; Decimal::cpp_type const one{1}; - Decimal::cpp_type const two{2}; + Decimal::cpp_type const three{3}; Decimal::cpp_type const min{std::numeric_limits::min()}; Decimal::cpp_type const max{std::numeric_limits::max()}; Decimal::cpp_type const big_number{"100000000000"}; @@ -34,8 +34,10 @@ TEST_SUITE("numeric op results") { // "If the number of digits in the mathematical result exceeds the number of digits // that the implementation retains for that operation, the result is truncated or rounded in an ·implementation-defined· manner." if (max != 0) { // if max == 0 -> unlimited - CHECK(Decimal::add(max, one) == max); - CHECK(Decimal::sub(max, -one) == max); + auto r = Decimal::div(one, three); + CHECK(r.has_value()); + CHECK(r.value() > zero); + CHECK(r.value() < one); } // "For xs:decimal operations, overflow behavior must raise a dynamic error [err:FOAR0002]." @@ -47,11 +49,14 @@ TEST_SUITE("numeric op results") { } // "On underflow, 0.0 must be returned." + /* + * XPath (and by extension SPARQL) do not define underflow themselves. + * But from how they use it, (see https://www.w3.org/TR/xpath-functions/#op.numeric ) it seems, they understand it the same as it is used in IEEE floats. + * And for these ( https://ieeemilestones.ethw.org/w/images/5/54/Handbook_Floating_Point_Chapter_3.pdf page 31) + * underflow occurs if the absolute of the result is not 0 but smaller than the smallest representable value. + */ if (min != 0) { // if min == 0 -> unlimited - CHECK(Decimal::sub(min, min) == zero); - CHECK(Decimal::div(min, two) == zero); - CHECK(Decimal::div(min, big_number) == zero); - CHECK(Decimal::mul(min, one / two) == zero); + CHECK(Decimal::div(one, max) == zero); } // https://www.w3.org/TR/xpath-functions/#func-numeric-divide diff --git a/tests/datatype/tests_PositiveInteger.cpp b/tests/datatype/tests_PositiveInteger.cpp index 7b74ba7fc..7d80576ed 100644 --- a/tests/datatype/tests_PositiveInteger.cpp +++ b/tests/datatype/tests_PositiveInteger.cpp @@ -6,6 +6,7 @@ using namespace rdf4cpp; TEST_CASE("Datatype PositiveInteger") { + CHECK(storage::default_node_storage.has_specialized_storage_for(datatypes::xsd::PositiveInteger::fixed_id)); constexpr auto correct_type_iri_cstr = "http://www.w3.org/2001/XMLSchema#positiveInteger"; diff --git a/tests/nodes/tests_Literal.cpp b/tests/nodes/tests_Literal.cpp index bac2a25b5..a69fcde89 100644 --- a/tests/nodes/tests_Literal.cpp +++ b/tests/nodes/tests_Literal.cpp @@ -218,7 +218,7 @@ TEST_CASE("Literal - casting") { } SUBCASE("non-integral") { - auto const lit1 = Literal::make_typed_from_value(rdf4cpp::BigDecimal(1.5)); + auto const lit1 = Literal::make_typed_from_value(rdf4cpp::Decimal128(1.5)); auto const lit2 = lit1.template cast(); CHECK_EQ(lit2.template value(), "1.5"); @@ -331,7 +331,7 @@ TEST_CASE("Literal - casting") { } SUBCASE("dec -> flt") { - auto const lit1 = Literal::make_typed_from_value(rdf4cpp::BigDecimal(1.0)); + auto const lit1 = Literal::make_typed_from_value(rdf4cpp::Decimal128(1.0)); auto const lit2 = lit1.template cast(); CHECK_EQ(lit2.datatype(), IRI{Float::identifier}); @@ -339,7 +339,7 @@ TEST_CASE("Literal - casting") { } SUBCASE("dec -> dbl") { - auto const lit1 = Literal::make_typed_from_value(rdf4cpp::BigDecimal(1.0)); + auto const lit1 = Literal::make_typed_from_value(rdf4cpp::Decimal128(1.0)); auto const lit2 = lit1.template cast(); CHECK_EQ(lit2.datatype(), IRI{Double::identifier}); @@ -347,7 +347,7 @@ TEST_CASE("Literal - casting") { } SUBCASE("dec -> int") { - auto const lit1 = Literal::make_typed_from_value(rdf4cpp::BigDecimal(1.2)); + auto const lit1 = Literal::make_typed_from_value(rdf4cpp::Decimal128(1.2)); auto const lit2 = lit1.template cast(); CHECK_EQ(lit2.datatype(), IRI{Int::identifier}); diff --git a/tests/nodes/tests_Literal_ops.cpp b/tests/nodes/tests_Literal_ops.cpp index abfa50bbf..4eac2a98f 100644 --- a/tests/nodes/tests_Literal_ops.cpp +++ b/tests/nodes/tests_Literal_ops.cpp @@ -160,8 +160,8 @@ TEST_CASE("Literal - logical ops") { TEST_CASE("Literal - numeric ops") { // simple promotion test cases - GENERATE_BINOP_TESTCASE(Float, 42.f, +, Decimal, rdf4cpp::BigDecimal{120.0}, Float, 162.f); - GENERATE_BINOP_TESTCASE(Decimal, rdf4cpp::BigDecimal{2.f}, *, Float, 120.0, Float, 240.f); + GENERATE_BINOP_TESTCASE(Float, 42.f, +, Decimal, rdf4cpp::Decimal128{120.0}, Float, 162.f); + GENERATE_BINOP_TESTCASE(Decimal, rdf4cpp::Decimal128{2.f}, *, Float, 120.0, Float, 240.f); GENERATE_BINOP_TESTCASE(Integer, 100, -, Float, 1.f, Float, 99.f); GENERATE_BINOP_TESTCASE(Float, 100.f, /, Integer, 2, Float, 50.f); @@ -173,9 +173,9 @@ TEST_CASE("Literal - numeric ops") { GENERATE_BINOP_TESTCASE(Integer, 1, +, Integer , 2, Integer, 3); GENERATE_BINOP_TESTCASE(Integer, 12, -, Integer, 1, Integer, 11); GENERATE_BINOP_TESTCASE(Integer, 3, *, Integer, 6, Integer, 18); - GENERATE_BINOP_TESTCASE(Integer, 12, /, Integer, 4, Decimal, rdf4cpp::BigDecimal{3.0}); + GENERATE_BINOP_TESTCASE(Integer, 12, /, Integer, 4, Decimal, rdf4cpp::Decimal128{3.0}); GENERATE_BINOP_TESTCASE(Int, 1, +, Integer, 3, Integer, 4); - GENERATE_BINOP_TESTCASE(Int, 1, +, Decimal, rdf4cpp::BigDecimal{2}, Decimal, rdf4cpp::BigDecimal{3}); + GENERATE_BINOP_TESTCASE(Int, 1, +, Decimal, rdf4cpp::Decimal128{2}, Decimal, rdf4cpp::Decimal128{3}); GENERATE_UNOP_TESTCASE(Integer, 1, +, Integer, 1); GENERATE_UNOP_TESTCASE(Integer, 1, -, Integer, -1); diff --git a/tests/nodes/tests_Node.cpp b/tests/nodes/tests_Node.cpp index 302e30f5e..613e2780a 100644 --- a/tests/nodes/tests_Node.cpp +++ b/tests/nodes/tests_Node.cpp @@ -85,7 +85,7 @@ TEST_SUITE("comparisons") { SUBCASE("nulls") { CHECK(Literal{} <=> Literal{} == std::partial_ordering::unordered); CHECK(Literal{} <=> Literal::make_typed_from_value(1) == std::partial_ordering::unordered); - CHECK(Literal::make_typed_from_value(rdf4cpp::BigDecimal(1.0)) <=> Literal{} == std::partial_ordering::unordered); + CHECK(Literal::make_typed_from_value(rdf4cpp::Decimal128(1.0)) <=> Literal{} == std::partial_ordering::unordered); } SUBCASE("inconvertibility") { @@ -103,7 +103,7 @@ TEST_SUITE("comparisons") { SUBCASE("conversion") { CHECK(Literal::make_typed_from_value(1) <=> Literal::make_typed_from_value(10) == std::partial_ordering::less); CHECK(Literal::make_typed_from_value(0) <=> Literal::make_typed_from_value(1.2f) == std::partial_ordering::less); - CHECK(Literal::make_typed_from_value(1.f) <=> Literal::make_typed_from_value(rdf4cpp::BigDecimal(1.0)) == std::partial_ordering::equivalent); + CHECK(Literal::make_typed_from_value(1.f) <=> Literal::make_typed_from_value(rdf4cpp::Decimal128(1.0)) == std::partial_ordering::equivalent); } } @@ -116,7 +116,7 @@ TEST_SUITE("comparisons") { CHECK(Literal::make_typed_from_value(10.f).order(Literal::make_typed_from_value(1)) == std::weak_ordering::less); // reason: decimal has fixed id and any fixed id type is always less than a dynamic one - CHECK(Literal::make_typed_from_value(1).order(Literal::make_typed_from_value(rdf4cpp::BigDecimal(10.0))) == std::weak_ordering::greater); + CHECK(Literal::make_typed_from_value(1).order(Literal::make_typed_from_value(rdf4cpp::Decimal128(10.0))) == std::weak_ordering::greater); } SUBCASE("nulls") { @@ -130,10 +130,10 @@ TEST_SUITE("comparisons") { SUBCASE("test type ordering extensions") { // expected: string < float < decimal < integer < int - CHECK(Literal::make_typed_from_value(rdf4cpp::BigDecimal(1.0)).order(Literal::make_typed_from_value(1)) == std::weak_ordering::greater); - CHECK(Literal::make_typed_from_value(rdf4cpp::BigDecimal(1.0)).order(Literal::make_typed_from_value(1)) == std::weak_ordering::less); - CHECK(Literal::make_typed_from_value(rdf4cpp::BigDecimal(1.0)).order(Literal::make_typed_from_value(1)) == std::weak_ordering::less); - CHECK(Literal::make_typed_from_value(rdf4cpp::BigDecimal(1.0)).order(Literal::make_typed_from_value("hello")) == std::weak_ordering::greater); + CHECK(Literal::make_typed_from_value(rdf4cpp::Decimal128(1.0)).order(Literal::make_typed_from_value(1)) == std::weak_ordering::greater); + CHECK(Literal::make_typed_from_value(rdf4cpp::Decimal128(1.0)).order(Literal::make_typed_from_value(1)) == std::weak_ordering::less); + CHECK(Literal::make_typed_from_value(rdf4cpp::Decimal128(1.0)).order(Literal::make_typed_from_value(1)) == std::weak_ordering::less); + CHECK(Literal::make_typed_from_value(rdf4cpp::Decimal128(1.0)).order(Literal::make_typed_from_value("hello")) == std::weak_ordering::greater); CHECK(Literal::make_typed_from_value(1.f).order(Literal::make_typed_from_value(1)) == std::weak_ordering::less); CHECK(Literal::make_typed_from_value(1.f).order(Literal::make_typed_from_value(1)) == std::weak_ordering::less); diff --git a/tests/serializer/tests_Serialize.cpp b/tests/serializer/tests_Serialize.cpp index 4a849aef5..879fd6b50 100644 --- a/tests/serializer/tests_Serialize.cpp +++ b/tests/serializer/tests_Serialize.cpp @@ -62,7 +62,7 @@ TEST_SUITE("Serialize") { run_ser_test(lit, "\"simple\""); } SUBCASE("non-inlined xsd:integer") { - auto lit = Literal::make_typed_from_value(xsd::Integer::cpp_type{"87960930222089"}); + auto lit = Literal::make_typed("87960930222089"); run_ser_test(lit, "\"87960930222089\"^^"); } SUBCASE("inlined xsd:int") { diff --git a/tests/util/tests_BigDecimal.cpp b/tests/util/tests_BigDecimal.cpp index 7def9719f..d9d89ad09 100644 --- a/tests/util/tests_BigDecimal.cpp +++ b/tests/util/tests_BigDecimal.cpp @@ -1,17 +1,241 @@ #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include +#include + #include +#include +#include + +using Dec = rdf4cpp::Decimal128; +using DecI = rdf4cpp::util::BigDecimal; +using RoundingMode = rdf4cpp::util::RoundingMode; + +#ifdef __SIZEOF_INT128__ +namespace doctest { + template<> struct StringMaker<__int128> { + static String convert(const __int128& value) { + std::stringstream s{}; + ::operator<<(s, value); + return doctest::String{s.view().data(), static_cast(s.view().size())}; + } + }; +} +static_assert(!rdf4cpp::util::detail::BoostNumber<__int128>); +#endif +static_assert(rdf4cpp::util::detail::BoostNumber); +static_assert(!rdf4cpp::util::detail::BoostNumber); + +TEST_CASE_TEMPLATE("checked arithmetic signed", T, int32_t, int64_t, __int128, boost::multiprecision::checked_int256_t) { + using namespace rdf4cpp::util::detail; + + SUBCASE("add") { + auto eq = [](auto a, auto b, auto r) { + T re; + CHECK(add_checked(T{a}, T{b}, re) == false); + CHECK(re == T{r}); + CHECK(add_checked(T{a}, T{b}, re) == false); + CHECK(re == T{r}); + }; + eq(5, 5, 10); + eq(5, -5, 0); + [[maybe_unused]] T re = 0; + CHECK(add_checked(std::numeric_limits::max(), T{1}, re) == true); + } + SUBCASE("sub") { + auto eq = [](auto a, auto b, auto r) { + T re; + CHECK(sub_checked(T{a}, T{b}, re) == false); + CHECK(re == T{r}); + CHECK(sub_checked(T{a}, T{b}, re) == false); + CHECK(re == T{r}); + }; + eq(5, -5, 10); + eq(5, 5, 0); + [[maybe_unused]] T re = 0; + CHECK(sub_checked(std::numeric_limits::max(), T{-1}, re) == true); + } + SUBCASE("mul") { + auto eq = [](auto a, auto b, auto r) { + T re; + CHECK(mul_checked(T{a}, T{b}, re) == false); + CHECK(re == T{r}); + CHECK(mul_checked(T{a}, T{b}, re) == false); + CHECK(re == T{r}); + }; + eq(5, 5, 25); + eq(5, -5, -25); + [[maybe_unused]] T re = 0; + CHECK(mul_checked(std::numeric_limits::max(), T{2}, re) == true); + } + SUBCASE("pow") { + auto eq = [](auto a, unsigned int b, auto r) { + T re; + CHECK(pow_checked(T{a}, b, re) == false); + CHECK(re == T{r}); + CHECK(pow_checked(T{a}, b, re) == false); + CHECK(re == T{r}); + }; + eq(5, 3, 25*5); + eq(5, 2, 25); + [[maybe_unused]] T re = 0; + CHECK(pow_checked(std::numeric_limits::max(), 2, re) == true); + } +} +TEST_CASE_TEMPLATE("checked arithmetic unsigned", T, uint32_t, uint64_t, unsigned __int128, boost::multiprecision::checked_uint256_t) { + using namespace rdf4cpp::util::detail; + + SUBCASE("add") { + auto eq = [](auto a, auto b, auto r) { + T re; + CHECK(add_checked(T{a}, T{b}, re) == false); + CHECK(re == T{r}); + CHECK(add_checked(T{a}, T{b}, re) == false); + CHECK(re == T{r}); + }; + eq(5u, 5u, 10u); + eq(5u, 10u, 15u); + [[maybe_unused]] T re = 0; + CHECK(add_checked(std::numeric_limits::max(), T{1}, re) == true); + } + SUBCASE("sub") { + auto eq = [](auto a, auto b, auto r) { + T re; + CHECK(sub_checked(T{a}, T{b}, re) == false); + CHECK(re == T{r}); + CHECK(sub_checked(T{a}, T{b}, re) == false); + CHECK(re == T{r}); + }; + eq(5u, 3u, 2u); + eq(5u, 5u, 0u); + [[maybe_unused]] T re = 0; + CHECK(sub_checked(T{0}, std::numeric_limits::max(), re) == true); + } + SUBCASE("mul") { + auto eq = [](auto a, auto b, auto r) { + T re; + CHECK(mul_checked(T{a}, T{b}, re) == false); + CHECK(re == T{r}); + CHECK(mul_checked(T{a}, T{b}, re) == false); + CHECK(re == T{r}); + }; + eq(5u, 5u, 25u); + eq(5u, 15u, 5u*15u); + [[maybe_unused]] T re = 0; + CHECK(mul_checked(std::numeric_limits::max(), T{2}, re) == true); + } + SUBCASE("pow") { + auto eq = [](auto a, unsigned int b, auto r) { + T re; + CHECK(pow_checked(T{a}, b, re) == false); + CHECK(re == T{r}); + CHECK(pow_checked(T{a}, b, re) == false); + CHECK(re == T{r}); + }; + eq(5u, 3u, 25u*5u); + eq(5u, 2u, 25u); + [[maybe_unused]] T re = 0; + CHECK(pow_checked(std::numeric_limits::max(), 2, re) == true); + } +} +TEST_CASE_TEMPLATE("checked casting", T, uint32_t, uint64_t, unsigned __int128, boost::multiprecision::checked_uint256_t, + int32_t, int64_t, __int128, boost::multiprecision::checked_int256_t ) { + using namespace rdf4cpp::util::detail; -using Dec = rdf4cpp::BigDecimal<>; -using DecI = rdf4cpp::BigDecimal; -using RoundingMode = rdf4cpp::RoundingMode; + static constexpr bool sign = std::numeric_limits::is_signed; + [[maybe_unused]] uint8_t u8 = 0; + [[maybe_unused]] int8_t i8 = 0; + [[maybe_unused]] T t{}; + CHECK(cast_checked(T{5}, i8) == false); + CHECK(i8 == 5); + CHECK(cast_checked(T{5}, u8) == false); + CHECK(u8 == 5); + if constexpr (sign) { + CHECK(cast_checked(T{-5}, i8) == false); + CHECK(i8 == -5); + CHECK(cast_checked(T{-5}, u8) == true); + CHECK(cast_checked(std::numeric_limits::min(), i8) == true); + if constexpr (IntegralExt) { + CHECK(cast_checked(std::numeric_limits::t>::max(), t) == true); + } + else { + CHECK(cast_checked(std::numeric_limits::t>::max(), t) == false); + CHECK(t == std::numeric_limits::max()); + } + } + CHECK(cast_checked(std::numeric_limits::max(), u8) == true); + CHECK(cast_checked(std::numeric_limits::max(), i8) == true); + CHECK(cast_checked(std::numeric_limits::max(), t) == false); + CHECK(t == std::numeric_limits::max()); +} + +constexpr __int128 Make128(int64_t h, int64_t l) { + constexpr __int128 p = []() { + __int128 r = 1; + for (int i = 0; i < std::numeric_limits::digits10; ++i) { + r *= 10; + } + return r; + }(); + return static_cast<__int128>(h) * p + l; +} + +TEST_CASE("int128 to_chars") { + auto tos = [](__int128 x) { + return rdf4cpp::writer::StringWriter::oneshot([x](rdf4cpp::writer::StringWriter& w) { + return rdf4cpp::util::to_chars_canonical(x, w); + }); + }; + CHECK(tos(0) == "0"); + CHECK(tos(std::numeric_limits<__int128>::max()) == "170141183460469231731687303715884105727"); + CHECK(tos(std::numeric_limits<__int128>::min()) == "-170141183460469231731687303715884105728"); + CHECK(tos(std::numeric_limits<__int128>::min()+1) == "-170141183460469231731687303715884105727"); + CHECK(Make128(5000, 5) > std::numeric_limits::max()); + CHECK(tos(Make128(1, 5)) == "1000000000000000005"); + CHECK(tos(Make128(5001, 5)) == "5001000000000000000005"); + CHECK(tos(Make128(5000, 5)) == "5000000000000000000005"); + CHECK(tos(Make128(5000, 500000)) == "5000000000000000500000"); + CHECK(tos(Make128(-1, -5)) == "-1000000000000000005"); + CHECK(tos(Make128(-5001, -5)) == "-5001000000000000000005"); + CHECK(tos(Make128(-5000, -5)) == "-5000000000000000000005"); + CHECK(tos(Make128(-5000, -500000)) == "-5000000000000000500000"); +} + +TEST_CASE("int128 from_chars") { + static constexpr rdf4cpp::datatypes::registry::util::ConstexprString s{"test"}; + CHECK(rdf4cpp::datatypes::registry::util::from_chars<__int128, s>("0") == 0); + CHECK(rdf4cpp::datatypes::registry::util::from_chars<__int128, s>("170141183460469231731687303715884105727") == std::numeric_limits<__int128>::max()); + CHECK(rdf4cpp::datatypes::registry::util::from_chars<__int128, s>("-170141183460469231731687303715884105728") == std::numeric_limits<__int128>::min()); + CHECK(rdf4cpp::datatypes::registry::util::from_chars<__int128, s>("-170141183460469231731687303715884105727") == std::numeric_limits<__int128>::min()+1); + CHECK(rdf4cpp::datatypes::registry::util::from_chars<__int128, s>("1000000000000000005") == Make128(1, 5)); + CHECK(rdf4cpp::datatypes::registry::util::from_chars<__int128, s>("5000000000000000000005") == Make128(5000, 5)); + CHECK(rdf4cpp::datatypes::registry::util::from_chars<__int128, s>("5001000000000000000005") == Make128(5001, 5)); + CHECK(rdf4cpp::datatypes::registry::util::from_chars<__int128, s>("5000000000000000500000") == Make128(5000, 500000)); + CHECK(rdf4cpp::datatypes::registry::util::from_chars<__int128, s>("-5001000000000000000005") == Make128(-5001, -5)); + CHECK(rdf4cpp::datatypes::registry::util::from_chars<__int128, s>("-5000000000000000000005") == Make128(-5000, -5)); + CHECK(rdf4cpp::datatypes::registry::util::from_chars<__int128, s>("-1000000000000000005") == Make128(-1, -5)); + CHECK(rdf4cpp::datatypes::registry::util::from_chars<__int128, s>("-5000000000000000500000") == Make128(-5000, -500000)); + + std::random_device rd{}; + std::default_random_engine r{std::uniform_int_distribution{}(rd)}; + std::uniform_int_distribution d{}; + std::array::digits10 + 2> buff; + for (int i = 0; i < 100000; ++i) { + __int128 const c = static_cast<__int128>(d(r)) << 64 | d(r); + auto char_res = rdf4cpp::util::to_chars(buff.data(), buff.data() + buff.size(), c); + CHECK(char_res.ec == std::errc{}); + auto len = char_res.ptr - buff.data(); + CHECK(len >= 0); + CHECK(len < buff.size()); + CHECK(c == rdf4cpp::datatypes::registry::util::from_chars<__int128, s>(std::string_view{buff.data(), static_cast(len)})); + } +} TEST_CASE("basics") { SUBCASE("ctor and compare") { - static_assert(rdf4cpp::BigDecimalBaseType); - static_assert(rdf4cpp::BigDecimalBaseType); - static_assert(rdf4cpp::BigDecimalBaseType); + static_assert(rdf4cpp::util::BigDecimalBaseType); + static_assert(rdf4cpp::util::BigDecimalBaseType); + static_assert(rdf4cpp::util::BigDecimalBaseType); Dec d{500, 1}; CHECK_GT(d, Dec{-500, 1}); CHECK(Dec{-500, 1} < d); @@ -205,19 +429,24 @@ TEST_CASE("conversion") { str << Dec{50, 1}; CHECK_EQ(str.view(), "5.0"); // uses string conversion, so no more tests here + str << rdf4cpp::Int128{100}; + CHECK_EQ(str.view(), "5.0100"); } SUBCASE("from double") { CHECK(Dec{50.0} == Dec{50, 0}); CHECK(Dec{-50.5} == Dec{-505, 1}); - CHECK(Dec{500000.0} == Dec{500000, 0}); + CHECK(Dec{5000000000000000000.0} == Dec{5000000000000000000, 0}); CHECK(Dec{0.0009765625} == Dec{"0.0009765625"}); CHECK(Dec{1.0} == Dec{1, 0}); + CHECK(Dec{1.2} != Dec{12, 1}); // 1.2 can not be exactly represented as double but can be as decimal CHECK_EQ(static_cast(Dec{1.0}), 1.0f); + CHECK_THROWS_AS([[maybe_unused]] auto _ = Dec(std::numeric_limits::max()), std::overflow_error); + CHECK(Dec(std::numeric_limits::min()) > Dec(0, 0)); + CHECK(Dec(std::numeric_limits::denorm_min()) > Dec(0, 0)); } SUBCASE("to double") { CHECK_EQ(static_cast(Dec{50, 0}), 50.0); CHECK_EQ(static_cast(Dec{500, 1}), 50.0); - CHECK_EQ(static_cast(Dec{static_cast(std::numeric_limits::max()) * 100, 2}), std::numeric_limits::max()); } SUBCASE("to float") { CHECK_EQ(static_cast(Dec{50, 0}), 50.0f); @@ -244,11 +473,16 @@ TEST_CASE("conversion") { CHECK_THROWS_AS(Dec{"5.5+5"}, rdf4cpp::InvalidNode); // no e notation allowed by rdf (xml) standard } - SUBCASE("from cpp_int") { - CHECK(Dec{boost::multiprecision::cpp_int{5}} == Dec{5, 0}); + SUBCASE("from Int128") { + CHECK(Dec{rdf4cpp::Int128{5}} == Dec{5, 0}); } - SUBCASE("to cpp_int") { - CHECK(static_cast(Dec{5, 0}) == boost::multiprecision::cpp_int{5}); - CHECK(static_cast(Dec{59, 1}) == boost::multiprecision::cpp_int{5}); + SUBCASE("to Int128") { + CHECK(static_cast(Dec{5, 0}) == rdf4cpp::Int128{5}); + CHECK(static_cast(Dec{59, 1}) == rdf4cpp::Int128{5}); + if constexpr (std::numeric_limits::is_bounded) { + CHECK(static_cast(Dec{std::numeric_limits::max(), std::numeric_limits::digits10}) == + (std::same_as ? rdf4cpp::Int128{3} : rdf4cpp::Int128{1}) + ); + } } }