diff --git a/doc/modules/ROOT/nav.adoc b/doc/modules/ROOT/nav.adoc index 23b2b01..82bda90 100644 --- a/doc/modules/ROOT/nav.adoc +++ b/doc/modules/ROOT/nav.adoc @@ -29,6 +29,7 @@ * xref:unsigned_integers.adoc[] * xref:signed_integers.adoc[] * xref:bounded_uint.adoc[] +* xref:bounded_int.adoc[] * xref:cuda.adoc[] * xref:literals.adoc[] * xref:limits.adoc[] diff --git a/doc/modules/ROOT/pages/api_reference.adoc b/doc/modules/ROOT/pages/api_reference.adoc index 3965107..ef9fb47 100644 --- a/doc/modules/ROOT/pages/api_reference.adoc +++ b/doc/modules/ROOT/pages/api_reference.adoc @@ -65,7 +65,7 @@ https://www.boost.org/LICENSE_1_0.txt | Safe signed 128-bit integer |=== -=== Bounded Unsigned Integer Type +=== Bounded Integer Types [cols="1,2", options="header"] |=== @@ -73,6 +73,9 @@ https://www.boost.org/LICENSE_1_0.txt | xref:bounded_uint.adoc[`bounded_uint`] | Safe unsigned integer constrained to a compile-time range `[Min, Max]` + +| xref:bounded_int.adoc[`bounded_int`] +| Safe signed integer constrained to a compile-time range `[Min, Max]` |=== === Enumerations diff --git a/doc/modules/ROOT/pages/bounded_int.adoc b/doc/modules/ROOT/pages/bounded_int.adoc new file mode 100644 index 0000000..fb6c366 --- /dev/null +++ b/doc/modules/ROOT/pages/bounded_int.adoc @@ -0,0 +1,122 @@ +//// +Copyright 2026 Matt Borland +Distributed under the Boost Software License, Version 1.0. +https://www.boost.org/LICENSE_1_0.txt +//// + +[#bounded_int] + += Bounded Signed Integer Types + +== Description + +`bounded_int` is a compile-time bounded signed integer type that enforces value constraints at both compile time and runtime. +The bounds `Min` and `Max` are non-type template parameters of any signed integer type (including the library's `i8`, `i16`, etc.). + +The underlying storage type (`basis_type`) is automatically selected as the smallest signed safe integer type that can represent the full `[Min, Max]` range: + +|=== +| Condition | Basis Type + +| Both bounds fit in `[-128, 127]` | `i8` +| Both bounds fit in `[-32768, 32767]` | `i16` +| Both bounds fit in `[-2^31, 2^31-1]` | `i32` +| Both bounds fit in `[-2^63, 2^63-1]` | `i64` +| Otherwise | `i128` +|=== + +== Synopsis + +[source,c++] +---- +#include + +namespace boost::safe_numbers { + +template + requires (valid_signed_bound && + valid_signed_bound && + signed_raw_value(Max) > signed_raw_value(Min)) +class bounded_int { +public: + using basis_type = /* automatically selected */; + + // Construction (throws std::domain_error if out of range) + explicit constexpr bounded_int(basis_type val); + explicit constexpr bounded_int(underlying_type val); + + // Conversions + template + explicit constexpr operator T() const; + + template + explicit constexpr operator bounded_int() const; + + explicit constexpr operator basis_type() const noexcept; + explicit constexpr operator underlying_type() const noexcept; + + // Comparison (defaulted three-way) + friend constexpr auto operator<=>(bounded_int, bounded_int) noexcept -> std::strong_ordering = default; + + // Unary operators + constexpr auto operator+() const noexcept -> bounded_int; + constexpr auto operator-() const -> bounded_int; // throws on min negation or out-of-bounds + + // Arithmetic operators (throw on overflow/underflow/out-of-range) + friend constexpr auto operator+(bounded_int, bounded_int) -> bounded_int; + friend constexpr auto operator-(bounded_int, bounded_int) -> bounded_int; + friend constexpr auto operator*(bounded_int, bounded_int) -> bounded_int; + friend constexpr auto operator/(bounded_int, bounded_int) -> bounded_int; + friend constexpr auto operator%(bounded_int, bounded_int) -> bounded_int; + + // Compound assignment + constexpr auto operator+=(bounded_int) -> bounded_int&; + constexpr auto operator-=(bounded_int) -> bounded_int&; + constexpr auto operator*=(bounded_int) -> bounded_int&; + constexpr auto operator/=(bounded_int) -> bounded_int&; + + // Increment / Decrement + constexpr auto operator++() -> bounded_int&; + constexpr auto operator++(int) -> bounded_int; + constexpr auto operator--() -> bounded_int&; + constexpr auto operator--(int) -> bounded_int; +}; + +} // namespace boost::safe_numbers +---- + +== Exception Behavior + +|=== +| Condition | Exception Type + +| Value outside `[Min, Max]` at construction or after arithmetic | `std::domain_error` +| Signed addition overflow | `std::overflow_error` +| Signed addition underflow | `std::underflow_error` +| Signed subtraction overflow | `std::overflow_error` +| Signed subtraction underflow | `std::underflow_error` +| Signed multiplication overflow | `std::overflow_error` +| Signed multiplication underflow | `std::underflow_error` +| Division by zero | `std::domain_error` +| Division `min / -1` overflow | `std::overflow_error` +| Modulo by zero | `std::domain_error` +| Modulo `min % -1` overflow | `std::overflow_error` +| Negation of type minimum | `std::overflow_error` +| Negation result outside bounds | `std::domain_error` +| Increment result exceeds Max | `std::domain_error` +| Decrement result below Min | `std::domain_error` +|=== + +== Mixed-Width Operations + +Operations between `bounded_int` types with different bounds are compile-time errors: + +[source,c++] +---- +bounded_int<-100, 100> a {50}; +bounded_int<-200, 200> b {50}; + +// auto c = a + b; // Compile error: different bounds +---- + +Bitwise operations are also compile-time errors on `bounded_int` types. diff --git a/include/boost/safe_numbers/bounded_integers.hpp b/include/boost/safe_numbers/bounded_integers.hpp index 3c2c18c..adaa3d7 100644 --- a/include/boost/safe_numbers/bounded_integers.hpp +++ b/include/boost/safe_numbers/bounded_integers.hpp @@ -11,6 +11,7 @@ #include #include #include +#include #ifndef BOOST_SAFE_NUMBERS_BUILD_MODULE @@ -39,6 +40,14 @@ auto to_string_val(T val) -> std::string return to_string(val); } +template +auto to_string_val(T val) -> std::string +{ + using std::to_string; + using boost::int128::to_string; + return to_string(val); +} + } // namespace detail template @@ -504,6 +513,677 @@ constexpr auto bounded_uint::operator--(int) -> bounded_uint } // namespace boost::safe_numbers +// ============================================================ +// bounded_int +// ============================================================ + +namespace boost::safe_numbers { + +template + requires (detail::valid_signed_bound && + detail::valid_signed_bound && + detail::signed_raw_value(Max) > detail::signed_raw_value(Min)) +class bounded_int +{ +public: + + using basis_type = std::conditional_t< + (detail::signed_raw_value(Min) >= std::numeric_limits::min() && + detail::signed_raw_value(Max) <= std::numeric_limits::max()), i8, + std::conditional_t< + (detail::signed_raw_value(Min) >= std::numeric_limits::min() && + detail::signed_raw_value(Max) <= std::numeric_limits::max()), i16, + std::conditional_t< + (detail::signed_raw_value(Min) >= std::numeric_limits::min() && + detail::signed_raw_value(Max) <= std::numeric_limits::max()), i32, + std::conditional_t< + (detail::signed_raw_value(Min) >= std::numeric_limits::min() && + detail::signed_raw_value(Max) <= std::numeric_limits::max()), i64, i128>>>>; + +private: + + using underlying_type = detail::underlying_type_t; + basis_type basis_ {}; + +public: + + explicit constexpr bounded_int(const basis_type val) + { + constexpr auto min_raw {static_cast(detail::signed_raw_value(Min))}; + constexpr auto max_raw {static_cast(detail::signed_raw_value(Max))}; + constexpr auto min_val {basis_type{min_raw}}; + constexpr auto max_val {basis_type{max_raw}}; + + if (val < min_val || val > max_val) + { + if (std::is_constant_evaluated()) + { + throw std::domain_error("bounded_int value out of range"); + } + else + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, "bounded_int value out of range"); + } + } + + basis_ = val; + } + + explicit constexpr bounded_int(const underlying_type val) : bounded_int{basis_type{val}} {} + + template + requires (detail::is_signed_library_type_v || detail::is_fundamental_signed_integral_v) + [[nodiscard]] explicit constexpr operator OtherBasis() const + { + const auto raw {static_cast>(basis_)}; + + if constexpr (sizeof(OtherBasis) < sizeof(basis_type)) + { + using raw_other = detail::underlying_type_t; + if (raw > static_cast>(std::numeric_limits::max()) || + raw < static_cast>(std::numeric_limits::min())) + { + if (std::is_constant_evaluated()) + { + throw std::domain_error("bounded_int conversion overflow"); + } + else + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, "bounded_int conversion overflow"); + } + } + + return static_cast(static_cast(raw)); + } + else + { + return static_cast(raw); + } + } + + template + [[nodiscard]] explicit constexpr operator bounded_int() const + { + using target_basis = typename bounded_int::basis_type; + using target_underlying = detail::underlying_type_t; + const auto raw {static_cast>(basis_)}; + return bounded_int{target_basis{static_cast(raw)}}; + } + + [[nodiscard]] explicit constexpr operator basis_type() const noexcept { return basis_; } + + [[nodiscard]] explicit constexpr operator underlying_type() const noexcept { return static_cast(basis_); } + + [[nodiscard]] friend constexpr auto operator<=>(bounded_int lhs, bounded_int rhs) noexcept + -> std::strong_ordering = default; + + [[nodiscard]] constexpr auto operator+() const noexcept -> bounded_int { return *this; } + + [[nodiscard]] constexpr auto operator-() const -> bounded_int; + + constexpr auto operator+=(bounded_int rhs) -> bounded_int&; + + constexpr auto operator-=(bounded_int rhs) -> bounded_int&; + + constexpr auto operator*=(bounded_int rhs) -> bounded_int&; + + constexpr auto operator/=(bounded_int rhs) -> bounded_int&; + + constexpr auto operator++() -> bounded_int&; + + constexpr auto operator++(int) -> bounded_int; + + constexpr auto operator--() -> bounded_int&; + + constexpr auto operator--(int) -> bounded_int; +}; + +// ------------------------------ +// Unary negation +// ------------------------------ + +template + requires (detail::valid_signed_bound && + detail::valid_signed_bound && + detail::signed_raw_value(Max) > detail::signed_raw_value(Min)) +constexpr auto bounded_int::operator-() const -> bounded_int +{ + using underlying = detail::underlying_type_t; + const auto raw {static_cast(basis_)}; + + if (raw == std::numeric_limits::min()) [[unlikely]] + { + if (std::is_constant_evaluated()) + { + throw std::overflow_error("bounded_int negation overflow"); + } + else + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, "bounded_int negation overflow"); + } + } + + const auto negated {static_cast(-raw)}; + return bounded_int{basis_type{negated}}; +} + +// ------------------------------ +// Addition +// ------------------------------ + +template +[[nodiscard]] constexpr auto operator+(const bounded_int lhs, + const bounded_int rhs) -> bounded_int +{ + using basis = typename bounded_int::basis_type; + using underlying = detail::underlying_type_t; + constexpr auto min_raw {static_cast(detail::signed_raw_value(Min))}; + constexpr auto max_raw {static_cast(detail::signed_raw_value(Max))}; + const auto lhs_raw {static_cast(static_cast(lhs))}; + const auto rhs_raw {static_cast(static_cast(rhs))}; + + underlying res {}; + const auto status {detail::impl::signed_no_intrin_add(lhs_raw, rhs_raw, res)}; + if (status == detail::impl::signed_overflow_status::overflow) + { + if (std::is_constant_evaluated()) + { + throw std::overflow_error("bounded_int addition overflow"); + } + else + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, "bounded_int addition overflow"); + } + } + else if (status == detail::impl::signed_overflow_status::underflow) + { + if (std::is_constant_evaluated()) + { + throw std::underflow_error("bounded_int addition underflow"); + } + else + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::underflow_error, "bounded_int addition underflow"); + } + } + + if (res < min_raw || res > max_raw) + { + if (std::is_constant_evaluated()) + { + throw std::domain_error("bounded_int addition result out of range"); + } + else + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, "bounded_int addition result out of range"); + } + } + + return bounded_int{basis{res}}; +} + +// ------------------------------ +// Subtraction +// ------------------------------ + +template +[[nodiscard]] constexpr auto operator-(const bounded_int lhs, + const bounded_int rhs) -> bounded_int +{ + using basis = typename bounded_int::basis_type; + using underlying = detail::underlying_type_t; + constexpr auto min_raw {static_cast(detail::signed_raw_value(Min))}; + constexpr auto max_raw {static_cast(detail::signed_raw_value(Max))}; + const auto lhs_raw {static_cast(static_cast(lhs))}; + const auto rhs_raw {static_cast(static_cast(rhs))}; + + underlying res {}; + const auto status {detail::impl::signed_no_intrin_sub(lhs_raw, rhs_raw, res)}; + if (status == detail::impl::signed_overflow_status::overflow) + { + if (std::is_constant_evaluated()) + { + throw std::overflow_error("bounded_int subtraction overflow"); + } + else + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, "bounded_int subtraction overflow"); + } + } + else if (status == detail::impl::signed_overflow_status::underflow) + { + if (std::is_constant_evaluated()) + { + throw std::underflow_error("bounded_int subtraction underflow"); + } + else + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::underflow_error, "bounded_int subtraction underflow"); + } + } + + if (res < min_raw || res > max_raw) + { + if (std::is_constant_evaluated()) + { + throw std::domain_error("bounded_int subtraction result out of range"); + } + else + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, "bounded_int subtraction result out of range"); + } + } + + return bounded_int{basis{res}}; +} + +// ------------------------------ +// Multiplication +// ------------------------------ + +template +[[nodiscard]] constexpr auto operator*(const bounded_int lhs, + const bounded_int rhs) -> bounded_int +{ + using basis = typename bounded_int::basis_type; + using underlying = detail::underlying_type_t; + constexpr auto min_raw {static_cast(detail::signed_raw_value(Min))}; + constexpr auto max_raw {static_cast(detail::signed_raw_value(Max))}; + const auto lhs_raw {static_cast(static_cast(lhs))}; + const auto rhs_raw {static_cast(static_cast(rhs))}; + + underlying res {}; + const auto status {detail::impl::signed_no_intrin_mul(lhs_raw, rhs_raw, res)}; + if (status == detail::impl::signed_overflow_status::overflow) + { + if (std::is_constant_evaluated()) + { + throw std::overflow_error("bounded_int multiplication overflow"); + } + else + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, "bounded_int multiplication overflow"); + } + } + else if (status == detail::impl::signed_overflow_status::underflow) + { + if (std::is_constant_evaluated()) + { + throw std::underflow_error("bounded_int multiplication underflow"); + } + else + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::underflow_error, "bounded_int multiplication underflow"); + } + } + + if (res < min_raw || res > max_raw) + { + if (std::is_constant_evaluated()) + { + throw std::domain_error("bounded_int multiplication result out of range"); + } + else + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, "bounded_int multiplication result out of range"); + } + } + + return bounded_int{basis{res}}; +} + +// ------------------------------ +// Division +// ------------------------------ + +template +[[nodiscard]] constexpr auto operator/(const bounded_int lhs, + const bounded_int rhs) -> bounded_int +{ + using basis = typename bounded_int::basis_type; + using underlying = detail::underlying_type_t; + constexpr auto min_raw {static_cast(detail::signed_raw_value(Min))}; + constexpr auto max_raw {static_cast(detail::signed_raw_value(Max))}; + const auto lhs_raw {static_cast(static_cast(lhs))}; + const auto rhs_raw {static_cast(static_cast(rhs))}; + + if (rhs_raw == static_cast(0)) [[unlikely]] + { + if (std::is_constant_evaluated()) + { + throw std::domain_error("bounded_int division by zero"); + } + else + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, "bounded_int division by zero"); + } + } + + if (lhs_raw == std::numeric_limits::min() && + rhs_raw == static_cast(-1)) [[unlikely]] + { + if (std::is_constant_evaluated()) + { + throw std::overflow_error("bounded_int division overflow"); + } + else + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, "bounded_int division overflow"); + } + } + + // Fast path: 0 / x = 0 + if (lhs_raw == static_cast(0)) + { + return bounded_int{basis{static_cast(0)}}; + } + + underlying res {}; + if constexpr (std::is_same_v || std::is_same_v) + { + res = static_cast(lhs_raw / rhs_raw); + } + else + { + res = lhs_raw / rhs_raw; + } + + if (res < min_raw || res > max_raw) + { + if (std::is_constant_evaluated()) + { + throw std::domain_error("bounded_int division result out of range"); + } + else + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, "bounded_int division result out of range"); + } + } + + return bounded_int{basis{res}}; +} + +// ------------------------------ +// Modulo +// ------------------------------ + +template +[[nodiscard]] constexpr auto operator%(const bounded_int lhs, + const bounded_int rhs) -> bounded_int +{ + using basis = typename bounded_int::basis_type; + using underlying = detail::underlying_type_t; + constexpr auto min_raw {static_cast(detail::signed_raw_value(Min))}; + constexpr auto max_raw {static_cast(detail::signed_raw_value(Max))}; + const auto lhs_raw {static_cast(static_cast(lhs))}; + const auto rhs_raw {static_cast(static_cast(rhs))}; + + if (rhs_raw == static_cast(0)) [[unlikely]] + { + if (std::is_constant_evaluated()) + { + throw std::domain_error("bounded_int modulo by zero"); + } + else + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, "bounded_int modulo by zero"); + } + } + + if (lhs_raw == std::numeric_limits::min() && + rhs_raw == static_cast(-1)) [[unlikely]] + { + if (std::is_constant_evaluated()) + { + throw std::overflow_error("bounded_int modulo overflow"); + } + else + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, "bounded_int modulo overflow"); + } + } + + // Fast path: 0 % x = 0 + if (lhs_raw == static_cast(0)) + { + return bounded_int{basis{static_cast(0)}}; + } + + underlying res {}; + if constexpr (std::is_same_v || std::is_same_v) + { + res = static_cast(lhs_raw % rhs_raw); + } + else + { + res = lhs_raw % rhs_raw; + } + + if (res < min_raw || res > max_raw) + { + if (std::is_constant_evaluated()) + { + throw std::domain_error("bounded_int modulo result out of range"); + } + else + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, "bounded_int modulo result out of range"); + } + } + + return bounded_int{basis{res}}; +} + +// ------------------------------ +// Compound assignment +// ------------------------------ + +template + requires (detail::valid_signed_bound && + detail::valid_signed_bound && + detail::signed_raw_value(Max) > detail::signed_raw_value(Min)) +constexpr auto bounded_int::operator+=(bounded_int rhs) -> bounded_int& +{ + *this = *this + rhs; + return *this; +} + +template + requires (detail::valid_signed_bound && + detail::valid_signed_bound && + detail::signed_raw_value(Max) > detail::signed_raw_value(Min)) +constexpr auto bounded_int::operator-=(bounded_int rhs) -> bounded_int& +{ + *this = *this - rhs; + return *this; +} + +template + requires (detail::valid_signed_bound && + detail::valid_signed_bound && + detail::signed_raw_value(Max) > detail::signed_raw_value(Min)) +constexpr auto bounded_int::operator*=(bounded_int rhs) -> bounded_int& +{ + *this = *this * rhs; + return *this; +} + +template + requires (detail::valid_signed_bound && + detail::valid_signed_bound && + detail::signed_raw_value(Max) > detail::signed_raw_value(Min)) +constexpr auto bounded_int::operator/=(bounded_int rhs) -> bounded_int& +{ + *this = *this / rhs; + return *this; +} + +// ------------------------------ +// Increment / Decrement +// ------------------------------ + +template + requires (detail::valid_signed_bound && + detail::valid_signed_bound && + detail::signed_raw_value(Max) > detail::signed_raw_value(Min)) +constexpr auto bounded_int::operator++() -> bounded_int& +{ + using underlying = detail::underlying_type_t; + constexpr auto max_raw {static_cast(detail::signed_raw_value(Max))}; + const auto raw {static_cast(static_cast(*this))}; + + underlying res {}; + const auto status {detail::impl::signed_no_intrin_add(raw, static_cast(1), res)}; + if (status != detail::impl::signed_overflow_status::no_error) + { + if (std::is_constant_evaluated()) + { + throw std::overflow_error("bounded_int increment overflow"); + } + else + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, "bounded_int increment overflow"); + } + } + + if (res > max_raw) + { + if (std::is_constant_evaluated()) + { + throw std::domain_error("bounded_int increment result out of range"); + } + else + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, "bounded_int increment result out of range"); + } + } + + *this = bounded_int{basis_type{res}}; + return *this; +} + +template + requires (detail::valid_signed_bound && + detail::valid_signed_bound && + detail::signed_raw_value(Max) > detail::signed_raw_value(Min)) +constexpr auto bounded_int::operator++(int) -> bounded_int +{ + auto tmp {*this}; + ++(*this); + return tmp; +} + +template + requires (detail::valid_signed_bound && + detail::valid_signed_bound && + detail::signed_raw_value(Max) > detail::signed_raw_value(Min)) +constexpr auto bounded_int::operator--() -> bounded_int& +{ + using underlying = detail::underlying_type_t; + constexpr auto min_raw {static_cast(detail::signed_raw_value(Min))}; + const auto raw {static_cast(static_cast(*this))}; + + underlying res {}; + const auto status {detail::impl::signed_no_intrin_sub(raw, static_cast(1), res)}; + if (status != detail::impl::signed_overflow_status::no_error) + { + if (std::is_constant_evaluated()) + { + throw std::underflow_error("bounded_int decrement underflow"); + } + else + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::underflow_error, "bounded_int decrement underflow"); + } + } + + if (res < min_raw) + { + if (std::is_constant_evaluated()) + { + throw std::domain_error("bounded_int decrement result out of range"); + } + else + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, "bounded_int decrement result out of range"); + } + } + + *this = bounded_int{basis_type{res}}; + return *this; +} + +template + requires (detail::valid_signed_bound && + detail::valid_signed_bound && + detail::signed_raw_value(Max) > detail::signed_raw_value(Min)) +constexpr auto bounded_int::operator--(int) -> bounded_int +{ + auto tmp {*this}; + --(*this); + return tmp; +} + +} // namespace boost::safe_numbers + +// Mixed-bounds blocking for bounded_int + +#define BOOST_SAFE_NUMBERS_DEFINE_MIXED_BOUNDED_INT_OP(OP_NAME, OP_SYMBOL) \ +template \ + requires (LHSMin != RHSMin || LHSMax != RHSMax) \ +constexpr auto OP_SYMBOL(const boost::safe_numbers::bounded_int, \ + const boost::safe_numbers::bounded_int) \ +{ \ + static_assert(boost::safe_numbers::detail::dependent_false< \ + boost::safe_numbers::bounded_int, \ + boost::safe_numbers::bounded_int>, \ + "Can not perform " OP_NAME " between bounded_int types with different bounds. " \ + "Both operands must have the same Min and Max."); \ + \ + return boost::safe_numbers::bounded_int( \ + typename boost::safe_numbers::bounded_int::basis_type{0}); \ +} + +namespace boost::safe_numbers { + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_BOUNDED_INT_OP("comparison", operator<=>) +BOOST_SAFE_NUMBERS_DEFINE_MIXED_BOUNDED_INT_OP("equality", operator==) +BOOST_SAFE_NUMBERS_DEFINE_MIXED_BOUNDED_INT_OP("addition", operator+) +BOOST_SAFE_NUMBERS_DEFINE_MIXED_BOUNDED_INT_OP("subtraction", operator-) +BOOST_SAFE_NUMBERS_DEFINE_MIXED_BOUNDED_INT_OP("multiplication", operator*) +BOOST_SAFE_NUMBERS_DEFINE_MIXED_BOUNDED_INT_OP("division", operator/) +BOOST_SAFE_NUMBERS_DEFINE_MIXED_BOUNDED_INT_OP("modulo", operator%) + +} // namespace boost::safe_numbers + +#undef BOOST_SAFE_NUMBERS_DEFINE_MIXED_BOUNDED_INT_OP + +// Bitwise operations blocking for bounded_int + +#define BOOST_SAFE_NUMBERS_DEFINE_BOUNDED_INT_BITWISE_OP(OP_NAME, OP_SYMBOL) \ +template \ +constexpr auto OP_SYMBOL(const boost::safe_numbers::bounded_int, \ + const boost::safe_numbers::bounded_int) \ +{ \ + static_assert(boost::safe_numbers::detail::dependent_false>, \ + "Can not perform " OP_NAME " between bounded_int types as the results would be non-sensical"); \ + \ + return boost::safe_numbers::bounded_int( \ + typename boost::safe_numbers::bounded_int::basis_type{0}); \ +} + +namespace boost::safe_numbers { + +BOOST_SAFE_NUMBERS_DEFINE_BOUNDED_INT_BITWISE_OP("and", operator&) +BOOST_SAFE_NUMBERS_DEFINE_BOUNDED_INT_BITWISE_OP("or", operator|) +BOOST_SAFE_NUMBERS_DEFINE_BOUNDED_INT_BITWISE_OP("xor", operator^) +BOOST_SAFE_NUMBERS_DEFINE_BOUNDED_INT_BITWISE_OP("right shift", operator>>) +BOOST_SAFE_NUMBERS_DEFINE_BOUNDED_INT_BITWISE_OP("left shift", operator<<) + +} // namespace boost::safe_numbers + +#undef BOOST_SAFE_NUMBERS_DEFINE_BOUNDED_INT_BITWISE_OP + #define BOOST_SAFE_NUMBERS_DEFINE_MIXED_BOUNDED_UINT_OP(OP_NAME, OP_SYMBOL) \ template \ requires (LHSMin != RHSMin || LHSMax != RHSMax) \ diff --git a/include/boost/safe_numbers/detail/type_traits.hpp b/include/boost/safe_numbers/detail/type_traits.hpp index 731ac6d..1be9346 100644 --- a/include/boost/safe_numbers/detail/type_traits.hpp +++ b/include/boost/safe_numbers/detail/type_traits.hpp @@ -118,6 +118,27 @@ constexpr auto raw_value(T val) noexcept } } +// valid_signed_bound concept + +template +concept valid_signed_bound = !std::is_same_v && (is_signed_library_type_v || is_fundamental_signed_integral_v); + +// signed_raw_value function + +template + requires valid_signed_bound +constexpr auto signed_raw_value(T val) noexcept +{ + if constexpr (is_signed_library_type_v) + { + return static_cast>(val); + } + else + { + return val; + } +} + } // namespace boost::safe_numbers::detail // Constrained forward declaration of bounded_uint @@ -131,6 +152,17 @@ class bounded_uint; } // namespace boost::safe_numbers +// Constrained forward declaration of bounded_int +namespace boost::safe_numbers { + +template + requires (detail::valid_signed_bound && + detail::valid_signed_bound && + detail::signed_raw_value(Max) > detail::signed_raw_value(Min)) +class bounded_int; + +} // namespace boost::safe_numbers + // bounded_uint specialization of is_unsigned_library_type namespace boost::safe_numbers::detail::impl { @@ -139,6 +171,14 @@ struct is_unsigned_library_type> : std::true_type {}; } // namespace boost::safe_numbers::detail::impl +// bounded_int specialization of is_signed_library_type +namespace boost::safe_numbers::detail::impl { + +template +struct is_signed_library_type> : std::true_type {}; + +} // namespace boost::safe_numbers::detail::impl + // is_bounded_type trait namespace boost::safe_numbers::detail { @@ -150,6 +190,9 @@ struct is_bounded_type : std::false_type {}; template struct is_bounded_type> : std::true_type {}; +template +struct is_bounded_type> : std::true_type {}; + } // namespace impl template @@ -171,6 +214,9 @@ struct is_library_type> : std::true_type {}; template struct is_library_type> : std::true_type {}; +template +struct is_library_type> : std::true_type {}; + template struct is_integral_library_type : std::false_type {}; @@ -183,6 +229,9 @@ struct is_integral_library_type> : std::true_type {}; template struct is_integral_library_type> : std::true_type {}; +template +struct is_integral_library_type> : std::true_type {}; + } // namespace impl template @@ -216,6 +265,12 @@ struct underlying> using type = typename underlying::basis_type>::type; }; +template +struct underlying> +{ + using type = typename underlying::basis_type>::type; +}; + } // namespace impl // Promotes an unsigned integer to the next higher type diff --git a/include/boost/safe_numbers/limits.hpp b/include/boost/safe_numbers/limits.hpp index 5238662..e976e47 100644 --- a/include/boost/safe_numbers/limits.hpp +++ b/include/boost/safe_numbers/limits.hpp @@ -158,6 +158,52 @@ class numeric_limits> static constexpr type denorm_min() { return min(); } }; +template +class numeric_limits> +{ + using type = boost::safe_numbers::bounded_int; + using underlying_type = boost::safe_numbers::detail::underlying_type_t; + +public: + static constexpr bool is_specialized = std::numeric_limits::is_specialized; + static constexpr bool is_signed = true; + static constexpr bool is_integer = true; + static constexpr bool is_exact = std::numeric_limits::is_exact; + static constexpr bool has_infinity = std::numeric_limits::has_infinity; + static constexpr bool has_quiet_NaN = std::numeric_limits::has_quiet_NaN; + static constexpr bool has_signaling_NaN = std::numeric_limits::has_signaling_NaN; + + #if ((!defined(_MSC_VER) && (__cplusplus <= 202002L)) || (defined(_MSC_VER) && (_MSVC_LANG <= 202002L))) + static constexpr std::float_denorm_style has_denorm = std::numeric_limits::has_denorm; + static constexpr bool has_denorm_loss = std::numeric_limits::has_denorm_loss; + #endif + + static constexpr std::float_round_style round_style = std::numeric_limits::round_style; + static constexpr bool is_iec559 = std::numeric_limits::is_iec559; + static constexpr bool is_bounded = std::numeric_limits::is_bounded; + static constexpr bool is_modulo = std::numeric_limits::is_modulo; + static constexpr int digits = std::numeric_limits::digits; + static constexpr int digits10 = std::numeric_limits::digits10; + static constexpr int max_digits10 = std::numeric_limits::max_digits10; + static constexpr int radix = std::numeric_limits::radix; + static constexpr int min_exponent = std::numeric_limits::min_exponent; + static constexpr int min_exponent10 = std::numeric_limits::min_exponent10; + static constexpr int max_exponent = std::numeric_limits::max_exponent; + static constexpr int max_exponent10 = std::numeric_limits::max_exponent10; + static constexpr bool traps = std::numeric_limits::traps; + static constexpr bool tinyness_before = std::numeric_limits::tinyness_before; + + static constexpr type min() { return type{static_cast(boost::safe_numbers::detail::signed_raw_value(Min))}; } + static constexpr type max() { return type{static_cast(boost::safe_numbers::detail::signed_raw_value(Max))}; } + static constexpr type lowest() { return min(); } + static constexpr type epsilon() { return min(); } + static constexpr type round_error() { return min(); } + static constexpr type infinity() { return min(); } + static constexpr type quiet_NaN() { return min(); } + static constexpr type signaling_NaN() { return min(); } + static constexpr type denorm_min() { return min(); } +}; + #ifdef __clang__ # pragma clang diagnostic pop #endif diff --git a/test/Jamfile b/test/Jamfile index 1d1ff6d..a5460db 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -141,6 +141,15 @@ run test_unsigned_bounded_fmt_format.cpp ; run test_unsigned_bounded_std_format.cpp ; compile-fail compile_fail_bounded_mixed_ops.cpp ; compile-fail compile_fail_bounded_bool_bounds.cpp ; +run test_signed_bounded_construction.cpp ; +run test_signed_bounded_addition.cpp ; +run test_signed_bounded_subtraction.cpp ; +run test_signed_bounded_multiplication.cpp ; +run test_signed_bounded_division.cpp ; +run test_signed_bounded_modulo.cpp ; +run test_signed_bounded_conversions.cpp ; +run test_signed_bounded_unary.cpp ; +compile-fail compile_fail_bounded_int_mixed_ops.cpp ; # Byte conversion tests run test_to_from_be.cpp ; diff --git a/test/compile_fail_bounded_int_mixed_ops.cpp b/test/compile_fail_bounded_int_mixed_ops.cpp new file mode 100644 index 0000000..0efab74 --- /dev/null +++ b/test/compile_fail_bounded_int_mixed_ops.cpp @@ -0,0 +1,22 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE +import boost.safe_numbers; +#else +#include +#endif + +using namespace boost::safe_numbers; + +int main() +{ + constexpr bounded_int<-100, 100> a {50}; + constexpr bounded_int<-200, 200> b {50}; + + // This should fail to compile: different bounds + const auto c {a + b}; + static_cast(c); + return 0; +} diff --git a/test/test_signed_bounded_addition.cpp b/test/test_signed_bounded_addition.cpp new file mode 100644 index 0000000..3b37611 --- /dev/null +++ b/test/test_signed_bounded_addition.cpp @@ -0,0 +1,297 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-result" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-result" +#elif defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable: 4834) +#endif + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include +#include + +#endif + +#include +#include + +using namespace boost::safe_numbers; + +// ----------------------------------------------- +// i8 basis: bounded_int<-100, 100> +// ----------------------------------------------- + +void test_i8_valid_addition() +{ + // Basic positive addition: 10 + 20 = 30 + constexpr bounded_int<-100, 100> a {i8{10}}; + constexpr bounded_int<-100, 100> b {i8{20}}; + const auto c {a + b}; + const bounded_int<-100, 100> expected {i8{30}}; + BOOST_TEST(c == expected); + + // Adding zero + constexpr bounded_int<-100, 100> zero {i8{0}}; + const auto d {a + zero}; + BOOST_TEST(d == a); + + // Negative + positive: -30 + 50 = 20 + constexpr bounded_int<-100, 100> e {i8{-30}}; + constexpr bounded_int<-100, 100> f {i8{50}}; + const auto g {e + f}; + const bounded_int<-100, 100> expected_g {i8{20}}; + BOOST_TEST(g == expected_g); + + // Negative + negative: -30 + -20 = -50 + constexpr bounded_int<-100, 100> h {i8{-30}}; + constexpr bounded_int<-100, 100> i {i8{-20}}; + const auto j {h + i}; + const bounded_int<-100, 100> expected_j {i8{-50}}; + BOOST_TEST(j == expected_j); + + // Max boundary: 50 + 50 = 100 + constexpr bounded_int<-100, 100> k {i8{50}}; + constexpr bounded_int<-100, 100> l {i8{50}}; + const auto m {k + l}; + const bounded_int<-100, 100> expected_m {i8{100}}; + BOOST_TEST(m == expected_m); + + // Min boundary: -50 + -50 = -100 + constexpr bounded_int<-100, 100> n {i8{-50}}; + constexpr bounded_int<-100, 100> o {i8{-50}}; + const auto p {n + o}; + const bounded_int<-100, 100> expected_p {i8{-100}}; + BOOST_TEST(p == expected_p); +} + +void test_i8_out_of_range_addition() +{ + // Positive out of range: 90 + 20 = 110 > 100 -> domain_error + constexpr bounded_int<-100, 100> a {i8{90}}; + constexpr bounded_int<-100, 100> b {i8{20}}; + BOOST_TEST_THROWS(a + b, std::domain_error); + + // Negative out of range: -90 + -20 = -110 < -100 -> domain_error + constexpr bounded_int<-100, 100> c {i8{-90}}; + constexpr bounded_int<-100, 100> d {i8{-20}}; + BOOST_TEST_THROWS(c + d, std::domain_error); + + // Just one over max: 51 + 50 = 101 > 100 + constexpr bounded_int<-100, 100> e {i8{51}}; + constexpr bounded_int<-100, 100> f {i8{50}}; + BOOST_TEST_THROWS(e + f, std::domain_error); + + // Just one below min: -51 + -50 = -101 < -100 + constexpr bounded_int<-100, 100> g {i8{-51}}; + constexpr bounded_int<-100, 100> h {i8{-50}}; + BOOST_TEST_THROWS(g + h, std::domain_error); +} + +void test_i8_overflow_addition() +{ + // Overflow of underlying i8: 100 + 100 = 200 > 127 (i8 max) -> overflow_error + constexpr bounded_int<-128, 127> a {i8{100}}; + constexpr bounded_int<-128, 127> b {i8{100}}; + BOOST_TEST_THROWS(a + b, std::overflow_error); + + // Underflow of underlying i8: -100 + -100 = -200 < -128 (i8 min) -> underflow_error + constexpr bounded_int<-128, 127> c {i8{-100}}; + constexpr bounded_int<-128, 127> d {i8{-100}}; + BOOST_TEST_THROWS(c + d, std::underflow_error); +} + +// ----------------------------------------------- +// i16 basis: bounded_int<-1000, 1000> +// ----------------------------------------------- + +void test_i16_valid_addition() +{ + constexpr bounded_int<-1000, 1000> a {i16{300}}; + constexpr bounded_int<-1000, 1000> b {i16{400}}; + const auto c {a + b}; + const bounded_int<-1000, 1000> expected {i16{700}}; + BOOST_TEST(c == expected); + + // Negative result: -600 + 200 = -400 + constexpr bounded_int<-1000, 1000> d {i16{-600}}; + constexpr bounded_int<-1000, 1000> e {i16{200}}; + const auto f {d + e}; + const bounded_int<-1000, 1000> expected_f {i16{-400}}; + BOOST_TEST(f == expected_f); + + // Max boundary: 500 + 500 = 1000 + constexpr bounded_int<-1000, 1000> g {i16{500}}; + constexpr bounded_int<-1000, 1000> h {i16{500}}; + const auto i {g + h}; + const bounded_int<-1000, 1000> expected_i {i16{1000}}; + BOOST_TEST(i == expected_i); +} + +void test_i16_out_of_range_addition() +{ + // 900 + 200 = 1100 > 1000 -> domain_error + constexpr bounded_int<-1000, 1000> a {i16{900}}; + constexpr bounded_int<-1000, 1000> b {i16{200}}; + BOOST_TEST_THROWS(a + b, std::domain_error); + + // -900 + -200 = -1100 < -1000 -> domain_error + constexpr bounded_int<-1000, 1000> c {i16{-900}}; + constexpr bounded_int<-1000, 1000> d {i16{-200}}; + BOOST_TEST_THROWS(c + d, std::domain_error); +} + +// ----------------------------------------------- +// i32 basis: bounded_int<-100000, 100000> +// ----------------------------------------------- + +void test_i32_valid_addition() +{ + constexpr bounded_int<-100000, 100000> a {i32{30000}}; + constexpr bounded_int<-100000, 100000> b {i32{40000}}; + const auto c {a + b}; + const bounded_int<-100000, 100000> expected {i32{70000}}; + BOOST_TEST(c == expected); + + // Negative: -50000 + 20000 = -30000 + constexpr bounded_int<-100000, 100000> d {i32{-50000}}; + constexpr bounded_int<-100000, 100000> e {i32{20000}}; + const auto f {d + e}; + const bounded_int<-100000, 100000> expected_f {i32{-30000}}; + BOOST_TEST(f == expected_f); +} + +void test_i32_out_of_range_addition() +{ + // 60000 + 60000 = 120000 > 100000 -> domain_error + constexpr bounded_int<-100000, 100000> a {i32{60000}}; + constexpr bounded_int<-100000, 100000> b {i32{60000}}; + BOOST_TEST_THROWS(a + b, std::domain_error); + + // -60000 + -60000 = -120000 < -100000 -> domain_error + constexpr bounded_int<-100000, 100000> c {i32{-60000}}; + constexpr bounded_int<-100000, 100000> d {i32{-60000}}; + BOOST_TEST_THROWS(c + d, std::domain_error); +} + +// ----------------------------------------------- +// i64 basis: bounded_int<-3000000000LL, 3000000000LL> +// ----------------------------------------------- + +void test_i64_valid_addition() +{ + constexpr bounded_int<-3000000000LL, 3000000000LL> a {i64{1000000000LL}}; + constexpr bounded_int<-3000000000LL, 3000000000LL> b {i64{1500000000LL}}; + const auto c {a + b}; + const bounded_int<-3000000000LL, 3000000000LL> expected {i64{2500000000LL}}; + BOOST_TEST(c == expected); +} + +void test_i64_out_of_range_addition() +{ + // 2000000000 + 2000000000 = 4000000000 > 3000000000 -> domain_error + constexpr bounded_int<-3000000000LL, 3000000000LL> a {i64{2000000000LL}}; + constexpr bounded_int<-3000000000LL, 3000000000LL> b {i64{2000000000LL}}; + BOOST_TEST_THROWS(a + b, std::domain_error); + + // -2000000000 + -2000000000 = -4000000000 < -3000000000 -> domain_error + constexpr bounded_int<-3000000000LL, 3000000000LL> c {i64{-2000000000LL}}; + constexpr bounded_int<-3000000000LL, 3000000000LL> d {i64{-2000000000LL}}; + BOOST_TEST_THROWS(c + d, std::domain_error); +} + +// ----------------------------------------------- +// Compound assignment += +// ----------------------------------------------- + +void test_compound_addition() +{ + // i8 basis + bounded_int<-100, 100> a {i8{10}}; + a += bounded_int<-100, 100>{i8{20}}; + const bounded_int<-100, 100> expected_a {i8{30}}; + BOOST_TEST(a == expected_a); + + // i16 basis + bounded_int<-1000, 1000> b {i16{300}}; + b += bounded_int<-1000, 1000>{i16{400}}; + const bounded_int<-1000, 1000> expected_b {i16{700}}; + BOOST_TEST(b == expected_b); + + // i32 basis + bounded_int<-100000, 100000> c {i32{30000}}; + c += bounded_int<-100000, 100000>{i32{40000}}; + const bounded_int<-100000, 100000> expected_c {i32{70000}}; + BOOST_TEST(c == expected_c); + + // i64 basis + bounded_int<-3000000000LL, 3000000000LL> d {i64{1000000000LL}}; + d += bounded_int<-3000000000LL, 3000000000LL>{i64{1500000000LL}}; + const bounded_int<-3000000000LL, 3000000000LL> expected_d {i64{2500000000LL}}; + BOOST_TEST(d == expected_d); + + // Compound assignment that throws + bounded_int<-100, 100> e {i8{90}}; + using bi100 = bounded_int<-100, 100>; + BOOST_TEST_THROWS(e += bi100{i8{20}}, std::domain_error); +} + +// ----------------------------------------------- +// Constexpr addition +// ----------------------------------------------- + +void test_constexpr_addition() +{ + constexpr bounded_int<-100, 100> a {i8{10}}; + constexpr bounded_int<-100, 100> b {i8{20}}; + constexpr auto c {a + b}; + constexpr bounded_int<-100, 100> expected {i8{30}}; + static_assert(c == expected); + + constexpr bounded_int<-1000, 1000> d {i16{300}}; + constexpr bounded_int<-1000, 1000> e {i16{400}}; + constexpr auto f {d + e}; + constexpr bounded_int<-1000, 1000> expected_f {i16{700}}; + static_assert(f == expected_f); + + constexpr bounded_int<-100000, 100000> g {i32{-25000}}; + constexpr bounded_int<-100000, 100000> h {i32{25000}}; + constexpr auto i {g + h}; + constexpr bounded_int<-100000, 100000> expected_i {i32{0}}; + static_assert(i == expected_i); +} + +int main() +{ + test_i8_valid_addition(); + test_i8_out_of_range_addition(); + test_i8_overflow_addition(); + + test_i16_valid_addition(); + test_i16_out_of_range_addition(); + + test_i32_valid_addition(); + test_i32_out_of_range_addition(); + + test_i64_valid_addition(); + test_i64_out_of_range_addition(); + + test_compound_addition(); + test_constexpr_addition(); + + return boost::report_errors(); +} diff --git a/test/test_signed_bounded_construction.cpp b/test/test_signed_bounded_construction.cpp new file mode 100644 index 0000000..a6a326c --- /dev/null +++ b/test/test_signed_bounded_construction.cpp @@ -0,0 +1,254 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include +#include + +#endif + +#include +#include + +using namespace boost::safe_numbers; + +// ----------------------------------------------- +// Static assertions on basis_type selection +// ----------------------------------------------- + +// Both bounds fit in [-128, 127] -> basis_type should be i8 +static_assert(std::is_same_v::basis_type, i8>); +static_assert(std::is_same_v::basis_type, i8>); +static_assert(std::is_same_v::basis_type, i8>); + +// Bounds exceed int8_t but fit in [-32768, 32767] -> basis_type should be i16 +static_assert(std::is_same_v::basis_type, i16>); +static_assert(std::is_same_v::basis_type, i16>); + +// Bounds exceed int16_t but fit in int32_t -> basis_type should be i32 +static_assert(std::is_same_v::basis_type, i32>); + +// Bounds exceed int32_t but fit in int64_t -> basis_type should be i64 +static_assert(std::is_same_v::basis_type, i64>); + +// ----------------------------------------------- +// sizeof checks - bounded_int should be the same +// size as the underlying safe type +// ----------------------------------------------- + +static_assert(sizeof(bounded_int<-100, 100>) == sizeof(i8)); +static_assert(sizeof(bounded_int<-200, 200>) == sizeof(i16)); +static_assert(sizeof(bounded_int<-100000, 100000>) == sizeof(i32)); +static_assert(sizeof(bounded_int<-3000000000LL, 3000000000LL>) == sizeof(i64)); + +// ----------------------------------------------- +// Valid construction at Min and Max boundaries +// ----------------------------------------------- + +void test_i8_boundary_construction() +{ + // Construct at exact Min boundary + constexpr bounded_int<-100, 100> a {i8{-100}}; + (void)a; + + // Construct at exact Max boundary + constexpr bounded_int<-100, 100> b {i8{100}}; + (void)b; + + // Construct at zero + constexpr bounded_int<-100, 100> c {i8{0}}; + (void)c; + + // Full int8_t range boundaries + constexpr bounded_int<-128, 127> d {i8{-128}}; + constexpr bounded_int<-128, 127> e {i8{127}}; + (void)d; + (void)e; + + // Non-negative range + constexpr bounded_int<0, 127> f {i8{0}}; + constexpr bounded_int<0, 127> g {i8{127}}; + (void)f; + (void)g; +} + +void test_i16_boundary_construction() +{ + constexpr bounded_int<-200, 200> a {i16{-200}}; + constexpr bounded_int<-200, 200> b {i16{200}}; + (void)a; + (void)b; + + constexpr bounded_int<-32768, 32767> c {i16{-32768}}; + constexpr bounded_int<-32768, 32767> d {i16{32767}}; + (void)c; + (void)d; +} + +void test_i32_boundary_construction() +{ + constexpr bounded_int<-100000, 100000> a {i32{-100000}}; + constexpr bounded_int<-100000, 100000> b {i32{100000}}; + (void)a; + (void)b; +} + +void test_i64_boundary_construction() +{ + constexpr bounded_int<-3000000000LL, 3000000000LL> a {i64{-3000000000LL}}; + constexpr bounded_int<-3000000000LL, 3000000000LL> b {i64{3000000000LL}}; + (void)a; + (void)b; +} + +// ----------------------------------------------- +// Out-of-range construction throws std::domain_error +// ----------------------------------------------- + +void test_i8_out_of_range() +{ + // Below Min + BOOST_TEST_THROWS((bounded_int<-100, 100>{i8{-101}}), std::domain_error); + BOOST_TEST_THROWS((bounded_int<-100, 100>{i8{-128}}), std::domain_error); + + // Above Max + BOOST_TEST_THROWS((bounded_int<-100, 100>{i8{101}}), std::domain_error); + BOOST_TEST_THROWS((bounded_int<-100, 100>{i8{127}}), std::domain_error); + + // Non-negative range: below Min + BOOST_TEST_THROWS((bounded_int<0, 127>{i8{-1}}), std::domain_error); + BOOST_TEST_THROWS((bounded_int<0, 127>{i8{-128}}), std::domain_error); +} + +void test_i16_out_of_range() +{ + // Below Min + BOOST_TEST_THROWS((bounded_int<-200, 200>{i16{-201}}), std::domain_error); + BOOST_TEST_THROWS((bounded_int<-200, 200>{i16{-32768}}), std::domain_error); + + // Above Max + BOOST_TEST_THROWS((bounded_int<-200, 200>{i16{201}}), std::domain_error); + BOOST_TEST_THROWS((bounded_int<-200, 200>{i16{32767}}), std::domain_error); +} + +void test_i32_out_of_range() +{ + // Below Min + BOOST_TEST_THROWS((bounded_int<-100000, 100000>{i32{-100001}}), std::domain_error); + + // Above Max + BOOST_TEST_THROWS((bounded_int<-100000, 100000>{i32{100001}}), std::domain_error); +} + +void test_i64_out_of_range() +{ + // Below Min + BOOST_TEST_THROWS((bounded_int<-3000000000LL, 3000000000LL>{i64{-3000000001LL}}), std::domain_error); + + // Above Max + BOOST_TEST_THROWS((bounded_int<-3000000000LL, 3000000000LL>{i64{3000000001LL}}), std::domain_error); +} + +// ----------------------------------------------- +// Construction from underlying_type +// ----------------------------------------------- + +void test_construction_from_underlying() +{ + // i8 basis: underlying is std::int8_t + bounded_int<-100, 100> a {static_cast(42)}; + const bounded_int<-100, 100> expected_a {i8{42}}; + BOOST_TEST(a == expected_a); + + // i16 basis: underlying is std::int16_t + bounded_int<-200, 200> b {static_cast(-150)}; + const bounded_int<-200, 200> expected_b {i16{-150}}; + BOOST_TEST(b == expected_b); + + // i32 basis: underlying is std::int32_t + bounded_int<-100000, 100000> c {static_cast(50000)}; + const bounded_int<-100000, 100000> expected_c {i32{50000}}; + BOOST_TEST(c == expected_c); + + // i64 basis: underlying is std::int64_t + bounded_int<-3000000000LL, 3000000000LL> d {static_cast(1000000000LL)}; + const bounded_int<-3000000000LL, 3000000000LL> expected_d {i64{1000000000LL}}; + BOOST_TEST(d == expected_d); +} + +// ----------------------------------------------- +// Constexpr construction and comparisons +// ----------------------------------------------- + +void test_constexpr_construction() +{ + constexpr bounded_int<-100, 100> a {i8{-50}}; + constexpr bounded_int<-100, 100> b {i8{50}}; + static_assert(a < b); + static_assert(a == a); + static_assert(b == b); + static_assert(a != b); + + constexpr bounded_int<-200, 200> c {i16{-200}}; + constexpr bounded_int<-200, 200> d {i16{200}}; + static_assert(c < d); + + constexpr bounded_int<-100000, 100000> e {i32{-100000}}; + constexpr bounded_int<-100000, 100000> f {i32{100000}}; + static_assert(e < f); + + constexpr bounded_int<-3000000000LL, 3000000000LL> g {i64{-3000000000LL}}; + constexpr bounded_int<-3000000000LL, 3000000000LL> h {i64{3000000000LL}}; + static_assert(g < h); +} + +// ----------------------------------------------- +// Comparison operators +// ----------------------------------------------- + +void test_comparisons() +{ + constexpr bounded_int<-100, 100> a {i8{-50}}; + constexpr bounded_int<-100, 100> b {i8{0}}; + constexpr bounded_int<-100, 100> c {i8{50}}; + constexpr bounded_int<-100, 100> d {i8{50}}; + + BOOST_TEST(a < b); + BOOST_TEST(b < c); + BOOST_TEST(a < c); + BOOST_TEST(c == d); + BOOST_TEST(c != a); + BOOST_TEST(c > a); + BOOST_TEST(a <= b); + BOOST_TEST(c >= b); + BOOST_TEST(c <= d); + BOOST_TEST(c >= d); +} + +int main() +{ + test_i8_boundary_construction(); + test_i16_boundary_construction(); + test_i32_boundary_construction(); + test_i64_boundary_construction(); + + test_i8_out_of_range(); + test_i16_out_of_range(); + test_i32_out_of_range(); + test_i64_out_of_range(); + + test_construction_from_underlying(); + test_constexpr_construction(); + test_comparisons(); + + return boost::report_errors(); +} diff --git a/test/test_signed_bounded_conversions.cpp b/test/test_signed_bounded_conversions.cpp new file mode 100644 index 0000000..e526cc5 --- /dev/null +++ b/test/test_signed_bounded_conversions.cpp @@ -0,0 +1,219 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +// Ignore [[nodiscard]] on the tests that we know are going to throw +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-result" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-result" +#elif defined(_MSC_VER) +# pragma warning (push) +# pragma warning (disable: 4834) +#endif + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include +#include + +#endif + +#include +#include + +using namespace boost::safe_numbers; + +// ----------------------------------------------- +// Conversion to basis_type (noexcept) +// ----------------------------------------------- + +void test_conversion_to_basis_type() +{ + constexpr bounded_int<-100, 100> a {i8{42}}; + const auto b {static_cast(a)}; + const i8 expected {42}; + BOOST_TEST(b == expected); + + constexpr bounded_int<-100, 100> c {i8{-73}}; + const auto d {static_cast(c)}; + const i8 expected_d {-73}; + BOOST_TEST(d == expected_d); +} + +// ----------------------------------------------- +// Conversion to underlying_type (noexcept) +// ----------------------------------------------- + +void test_conversion_to_underlying_type() +{ + constexpr bounded_int<-100, 100> a {i8{42}}; + const auto b {static_cast(a)}; + BOOST_TEST(b == static_cast(42)); + + constexpr bounded_int<-100, 100> c {i8{-73}}; + const auto d {static_cast(c)}; + BOOST_TEST(d == static_cast(-73)); +} + +// ----------------------------------------------- +// Widening conversion (basis to larger signed type) +// ----------------------------------------------- + +void test_widening_conversion() +{ + constexpr bounded_int<-100, 100> a {i8{42}}; + const auto b {static_cast(a)}; + const i16 expected {42}; + BOOST_TEST(b == expected); + + const auto c {static_cast(a)}; + const i32 expected_c {42}; + BOOST_TEST(c == expected_c); + + const auto d {static_cast(a)}; + const i64 expected_d {42}; + BOOST_TEST(d == expected_d); +} + +void test_widening_negative() +{ + constexpr bounded_int<-100, 100> a {i8{-100}}; + const auto b {static_cast(a)}; + const i16 expected {-100}; + BOOST_TEST(b == expected); +} + +// ----------------------------------------------- +// Narrowing conversion (larger basis to smaller type) +// ----------------------------------------------- + +void test_narrowing_conversion_fits() +{ + // bounded_int<-1000, 1000> has i16 basis, convert to i8 with value 42 (fits) + constexpr bounded_int<-1000, 1000> a {i16{42}}; + const auto b {static_cast(a)}; + const i8 expected {42}; + BOOST_TEST(b == expected); + + // Negative value that fits + constexpr bounded_int<-1000, 1000> c {i16{-100}}; + const auto d {static_cast(c)}; + const i8 expected_d {-100}; + BOOST_TEST(d == expected_d); +} + +void test_narrowing_conversion_throws() +{ + // Value 500 exceeds int8_t max (127) + constexpr bounded_int<-1000, 1000> a {i16{500}}; + BOOST_TEST_THROWS((static_cast(a)), std::domain_error); + + // Value -500 is below int8_t min (-128) + constexpr bounded_int<-1000, 1000> b {i16{-500}}; + BOOST_TEST_THROWS((static_cast(b)), std::domain_error); +} + +// ----------------------------------------------- +// Cross-bounded conversion (different bounds, same or different basis) +// ----------------------------------------------- + +void test_cross_bounded_fits() +{ + constexpr bounded_int<-100, 100> a {i8{42}}; + const auto b {static_cast>(a)}; + const bounded_int<-50, 50> expected {i8{42}}; + BOOST_TEST(b == expected); +} + +void test_cross_bounded_throws() +{ + constexpr bounded_int<-100, 100> a {i8{75}}; + BOOST_TEST_THROWS((static_cast>(a)), std::domain_error); + + constexpr bounded_int<-100, 100> b {i8{-75}}; + BOOST_TEST_THROWS((static_cast>(b)), std::domain_error); +} + +void test_cross_bounded_wider_to_narrower() +{ + // [−200, 200] (i16 basis) → [−50, 50] (i8 basis): value 25 fits + constexpr bounded_int<-200, 200> a {i16{25}}; + const auto b {static_cast>(a)}; + const bounded_int<-50, 50> expected {i8{25}}; + BOOST_TEST(b == expected); +} + +void test_cross_bounded_wider_to_narrower_throws() +{ + // [−200, 200] (i16 basis) → [−50, 50] (i8 basis): value 100 exceeds target max + constexpr bounded_int<-200, 200> a {i16{100}}; + BOOST_TEST_THROWS((static_cast>(a)), std::domain_error); +} + +// ----------------------------------------------- +// Identity conversion (same bounds) +// ----------------------------------------------- + +void test_identity_conversion() +{ + constexpr bounded_int<-100, 100> a {i8{42}}; + const auto b {static_cast>(a)}; + BOOST_TEST(a == b); + + constexpr bounded_int<-100, 100> c {i8{-100}}; + const auto d {static_cast>(c)}; + BOOST_TEST(c == d); +} + +// ----------------------------------------------- +// Constexpr conversions +// ----------------------------------------------- + +void test_constexpr_conversions() +{ + // basis_type conversion + constexpr bounded_int<-100, 100> a {i8{42}}; + constexpr auto b {static_cast(a)}; + static_assert(b == i8{42}); + + // widening + constexpr auto c {static_cast(a)}; + static_assert(c == i16{42}); + + // cross-bounded + constexpr auto d {static_cast>(a)}; + constexpr bounded_int<-50, 50> expected_d {i8{42}}; + static_assert(d == expected_d); +} + +int main() +{ + test_conversion_to_basis_type(); + test_conversion_to_underlying_type(); + + test_widening_conversion(); + test_widening_negative(); + + test_narrowing_conversion_fits(); + test_narrowing_conversion_throws(); + + test_cross_bounded_fits(); + test_cross_bounded_throws(); + test_cross_bounded_wider_to_narrower(); + test_cross_bounded_wider_to_narrower_throws(); + + test_identity_conversion(); + test_constexpr_conversions(); + + return boost::report_errors(); +} diff --git a/test/test_signed_bounded_division.cpp b/test/test_signed_bounded_division.cpp new file mode 100644 index 0000000..09fab95 --- /dev/null +++ b/test/test_signed_bounded_division.cpp @@ -0,0 +1,132 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +// Ignore [[nodiscard]] on the tests that we know are going to throw +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-result" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-result" +#elif defined(_MSC_VER) +# pragma warning (push) +# pragma warning (disable: 4834) +#endif + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include +#include + +#endif + +#include +#include + +using namespace boost::safe_numbers; + +void test_valid_division() +{ + constexpr bounded_int<-100, 100> a {i8{50}}; + constexpr bounded_int<-100, 100> b {i8{5}}; + const auto c {a / b}; + const bounded_int<-100, 100> expected {i8{10}}; + BOOST_TEST(c == expected); +} + +void test_division_by_zero() +{ + constexpr bounded_int<-100, 100> a {i8{10}}; + constexpr bounded_int<-100, 100> zero {i8{0}}; + BOOST_TEST_THROWS(a / zero, std::domain_error); +} + +void test_min_div_neg_one_overflow() +{ + // -128 / -1 = 128, which overflows int8_t max (127) + constexpr bounded_int<-128, 127> a {i8{-128}}; + constexpr bounded_int<-128, 127> neg_one {i8{-1}}; + BOOST_TEST_THROWS(a / neg_one, std::overflow_error); +} + +void test_zero_divided_by_one() +{ + constexpr bounded_int<-100, 100> a {i8{0}}; + constexpr bounded_int<-100, 100> one {i8{1}}; + const auto c {a / one}; + const bounded_int<-100, 100> expected {i8{0}}; + BOOST_TEST(c == expected); +} + +void test_negative_division() +{ + constexpr bounded_int<-100, 100> a {i8{-50}}; + constexpr bounded_int<-100, 100> b {i8{5}}; + const auto c {a / b}; + const bounded_int<-100, 100> expected {i8{-10}}; + BOOST_TEST(c == expected); +} + +void test_compound_assignment_division() +{ + bounded_int<-100, 100> a {i8{50}}; + const bounded_int<-100, 100> b {i8{5}}; + a /= b; + const bounded_int<-100, 100> expected {i8{10}}; + BOOST_TEST(a == expected); +} + +void test_self_division() +{ + constexpr bounded_int<-100, 100> a {i8{42}}; + const auto c {a / a}; + const bounded_int<-100, 100> expected {i8{1}}; + BOOST_TEST(c == expected); +} + +void test_divide_by_one_identity() +{ + constexpr bounded_int<-100, 100> a {i8{-73}}; + constexpr bounded_int<-100, 100> one {i8{1}}; + const auto c {a / one}; + BOOST_TEST(c == a); +} + +void test_truncation() +{ + // Integer division truncates toward zero: 7 / 2 = 3 + constexpr bounded_int<-100, 100> a {i8{7}}; + constexpr bounded_int<-100, 100> b {i8{2}}; + const auto c {a / b}; + const bounded_int<-100, 100> expected {i8{3}}; + BOOST_TEST(c == expected); + + // -7 / 2 = -3 (truncation toward zero) + constexpr bounded_int<-100, 100> d {i8{-7}}; + const auto e {d / b}; + const bounded_int<-100, 100> expected_e {i8{-3}}; + BOOST_TEST(e == expected_e); +} + +int main() +{ + test_valid_division(); + test_division_by_zero(); + test_min_div_neg_one_overflow(); + test_zero_divided_by_one(); + test_negative_division(); + test_compound_assignment_division(); + test_self_division(); + test_divide_by_one_identity(); + test_truncation(); + + return boost::report_errors(); +} diff --git a/test/test_signed_bounded_modulo.cpp b/test/test_signed_bounded_modulo.cpp new file mode 100644 index 0000000..2a6f46e --- /dev/null +++ b/test/test_signed_bounded_modulo.cpp @@ -0,0 +1,128 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +// Ignore [[nodiscard]] on the tests that we know are going to throw +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-result" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-result" +#elif defined(_MSC_VER) +# pragma warning (push) +# pragma warning (disable: 4834) +#endif + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include +#include + +#endif + +#include +#include + +using namespace boost::safe_numbers; + +void test_valid_modulo() +{ + constexpr bounded_int<-100, 100> a {i8{53}}; + constexpr bounded_int<-100, 100> b {i8{10}}; + const auto c {a % b}; + const bounded_int<-100, 100> expected {i8{3}}; + BOOST_TEST(c == expected); +} + +void test_mod_by_zero() +{ + constexpr bounded_int<-100, 100> a {i8{10}}; + constexpr bounded_int<-100, 100> zero {i8{0}}; + BOOST_TEST_THROWS(a % zero, std::domain_error); +} + +void test_min_mod_neg_one_overflow() +{ + // -128 % -1 is undefined behavior for int8_t; the library throws overflow + constexpr bounded_int<-128, 127> a {i8{-128}}; + constexpr bounded_int<-128, 127> neg_one {i8{-1}}; + BOOST_TEST_THROWS(a % neg_one, std::overflow_error); +} + +void test_negative_modulo() +{ + // C++ truncation toward zero: -53 % 10 = -3 + constexpr bounded_int<-100, 100> a {i8{-53}}; + constexpr bounded_int<-100, 100> b {i8{10}}; + const auto c {a % b}; + const bounded_int<-100, 100> expected {i8{-3}}; + BOOST_TEST(c == expected); +} + +void test_zero_mod() +{ + // 0 % x = 0 (fast path) + constexpr bounded_int<-100, 100> a {i8{0}}; + constexpr bounded_int<-100, 100> b {i8{7}}; + const auto c {a % b}; + const bounded_int<-100, 100> expected {i8{0}}; + BOOST_TEST(c == expected); +} + +void test_mod_by_one() +{ + // Any value % 1 = 0 + constexpr bounded_int<-100, 100> a {i8{42}}; + constexpr bounded_int<-100, 100> one {i8{1}}; + const auto c {a % one}; + const bounded_int<-100, 100> expected {i8{0}}; + BOOST_TEST(c == expected); +} + +void test_mod_exact_division() +{ + // 50 % 10 = 0 (exact division) + constexpr bounded_int<-100, 100> a {i8{50}}; + constexpr bounded_int<-100, 100> b {i8{10}}; + const auto c {a % b}; + const bounded_int<-100, 100> expected {i8{0}}; + BOOST_TEST(c == expected); +} + +void test_negative_divisor() +{ + // 53 % -10 = 3 (sign follows dividend in C++) + constexpr bounded_int<-100, 100> a {i8{53}}; + constexpr bounded_int<-100, 100> b {i8{-10}}; + const auto c {a % b}; + const bounded_int<-100, 100> expected {i8{3}}; + BOOST_TEST(c == expected); + + // -53 % -10 = -3 + constexpr bounded_int<-100, 100> d {i8{-53}}; + const auto e {d % b}; + const bounded_int<-100, 100> expected_e {i8{-3}}; + BOOST_TEST(e == expected_e); +} + +int main() +{ + test_valid_modulo(); + test_mod_by_zero(); + test_min_mod_neg_one_overflow(); + test_negative_modulo(); + test_zero_mod(); + test_mod_by_one(); + test_mod_exact_division(); + test_negative_divisor(); + + return boost::report_errors(); +} diff --git a/test/test_signed_bounded_multiplication.cpp b/test/test_signed_bounded_multiplication.cpp new file mode 100644 index 0000000..c9f1ce8 --- /dev/null +++ b/test/test_signed_bounded_multiplication.cpp @@ -0,0 +1,315 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-result" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-result" +#elif defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable: 4834) +#endif + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include +#include + +#endif + +#include +#include + +using namespace boost::safe_numbers; + +// ----------------------------------------------- +// i8 basis: bounded_int<-100, 100> +// ----------------------------------------------- + +void test_i8_valid_multiplication() +{ + // Basic: 5 * 10 = 50 + constexpr bounded_int<-100, 100> a {i8{5}}; + constexpr bounded_int<-100, 100> b {i8{10}}; + const auto c {a * b}; + const bounded_int<-100, 100> expected {i8{50}}; + BOOST_TEST(c == expected); + + // Multiply by zero + constexpr bounded_int<-100, 100> zero {i8{0}}; + const auto d {a * zero}; + const bounded_int<-100, 100> expected_d {i8{0}}; + BOOST_TEST(d == expected_d); + + // Multiply by one + constexpr bounded_int<-100, 100> one {i8{1}}; + const auto e {a * one}; + BOOST_TEST(e == a); + + // Negative result: -5 * 10 = -50 + constexpr bounded_int<-100, 100> f {i8{-5}}; + constexpr bounded_int<-100, 100> g {i8{10}}; + const auto h {f * g}; + const bounded_int<-100, 100> expected_h {i8{-50}}; + BOOST_TEST(h == expected_h); + + // Negative * negative = positive: -5 * -10 = 50 + constexpr bounded_int<-100, 100> i {i8{-5}}; + constexpr bounded_int<-100, 100> j {i8{-10}}; + const auto k {i * j}; + const bounded_int<-100, 100> expected_k {i8{50}}; + BOOST_TEST(k == expected_k); + + // Max boundary: 10 * 10 = 100 + constexpr bounded_int<-100, 100> l {i8{10}}; + constexpr bounded_int<-100, 100> m {i8{10}}; + const auto n {l * m}; + const bounded_int<-100, 100> expected_n {i8{100}}; + BOOST_TEST(n == expected_n); + + // Min boundary: -10 * 10 = -100 + constexpr bounded_int<-100, 100> o {i8{-10}}; + constexpr bounded_int<-100, 100> p {i8{10}}; + const auto q {o * p}; + const bounded_int<-100, 100> expected_q {i8{-100}}; + BOOST_TEST(q == expected_q); +} + +void test_i8_out_of_range_positive() +{ + // 50 * 3 = 150 > 127 (i8 max) -> overflow_error (underlying overflow fires first) + constexpr bounded_int<-100, 100> a {i8{50}}; + constexpr bounded_int<-100, 100> b {i8{3}}; + BOOST_TEST_THROWS(a * b, std::overflow_error); + + // Use wider type to test domain_error (result fits in type but exceeds bounds) + // bounded_int<-100, 100> with i8 basis: 11 * 10 = 110 > 100, but 110 < 127 so no underlying overflow + constexpr bounded_int<-100, 100> c {i8{11}}; + constexpr bounded_int<-100, 100> d {i8{10}}; + BOOST_TEST_THROWS(c * d, std::domain_error); +} + +void test_i8_out_of_range_negative() +{ + // -50 * 3 = -150 < -128 (i8 min) -> underflow_error (underlying underflow fires first) + constexpr bounded_int<-100, 100> a {i8{-50}}; + constexpr bounded_int<-100, 100> b {i8{3}}; + BOOST_TEST_THROWS(a * b, std::underflow_error); + + // Just one below: -34 * 3 = -102 < -100 + constexpr bounded_int<-100, 100> c {i8{-34}}; + constexpr bounded_int<-100, 100> d {i8{3}}; + BOOST_TEST_THROWS(c * d, std::domain_error); +} + +void test_i8_underlying_overflow() +{ + // 100 * 100 = 10000 overflows i8 range [-128, 127] + constexpr bounded_int<-128, 127> a {i8{100}}; + constexpr bounded_int<-128, 127> b {i8{100}}; + BOOST_TEST_THROWS(a * b, std::overflow_error); + + // -100 * 100 = -10000 underflows i8 range + constexpr bounded_int<-128, 127> c {i8{-100}}; + constexpr bounded_int<-128, 127> d {i8{100}}; + BOOST_TEST_THROWS(c * d, std::underflow_error); +} + +// ----------------------------------------------- +// i16 basis: bounded_int<-1000, 1000> +// ----------------------------------------------- + +void test_i16_valid_multiplication() +{ + constexpr bounded_int<-1000, 1000> a {i16{25}}; + constexpr bounded_int<-1000, 1000> b {i16{30}}; + const auto c {a * b}; + const bounded_int<-1000, 1000> expected {i16{750}}; + BOOST_TEST(c == expected); + + // Negative: -20 * 40 = -800 + constexpr bounded_int<-1000, 1000> d {i16{-20}}; + constexpr bounded_int<-1000, 1000> e {i16{40}}; + const auto f {d * e}; + const bounded_int<-1000, 1000> expected_f {i16{-800}}; + BOOST_TEST(f == expected_f); + + // Max boundary: 100 * 10 = 1000 + constexpr bounded_int<-1000, 1000> g {i16{100}}; + constexpr bounded_int<-1000, 1000> h {i16{10}}; + const auto i {g * h}; + const bounded_int<-1000, 1000> expected_i {i16{1000}}; + BOOST_TEST(i == expected_i); +} + +void test_i16_out_of_range_multiplication() +{ + // 500 * 3 = 1500 > 1000 -> domain_error + constexpr bounded_int<-1000, 1000> a {i16{500}}; + constexpr bounded_int<-1000, 1000> b {i16{3}}; + BOOST_TEST_THROWS(a * b, std::domain_error); + + // -500 * 3 = -1500 < -1000 -> domain_error + constexpr bounded_int<-1000, 1000> c {i16{-500}}; + constexpr bounded_int<-1000, 1000> d {i16{3}}; + BOOST_TEST_THROWS(c * d, std::domain_error); +} + +// ----------------------------------------------- +// i32 basis: bounded_int<-100000, 100000> +// ----------------------------------------------- + +void test_i32_valid_multiplication() +{ + constexpr bounded_int<-100000, 100000> a {i32{200}}; + constexpr bounded_int<-100000, 100000> b {i32{300}}; + const auto c {a * b}; + const bounded_int<-100000, 100000> expected {i32{60000}}; + BOOST_TEST(c == expected); + + // Negative: -200 * 300 = -60000 + constexpr bounded_int<-100000, 100000> d {i32{-200}}; + constexpr bounded_int<-100000, 100000> e {i32{300}}; + const auto f {d * e}; + const bounded_int<-100000, 100000> expected_f {i32{-60000}}; + BOOST_TEST(f == expected_f); +} + +void test_i32_out_of_range_multiplication() +{ + // 500 * 300 = 150000 > 100000 -> domain_error + constexpr bounded_int<-100000, 100000> a {i32{500}}; + constexpr bounded_int<-100000, 100000> b {i32{300}}; + BOOST_TEST_THROWS(a * b, std::domain_error); + + // -500 * 300 = -150000 < -100000 -> domain_error + constexpr bounded_int<-100000, 100000> c {i32{-500}}; + constexpr bounded_int<-100000, 100000> d {i32{300}}; + BOOST_TEST_THROWS(c * d, std::domain_error); +} + +// ----------------------------------------------- +// i64 basis: bounded_int<-3000000000LL, 3000000000LL> +// ----------------------------------------------- + +void test_i64_valid_multiplication() +{ + constexpr bounded_int<-3000000000LL, 3000000000LL> a {i64{50000LL}}; + constexpr bounded_int<-3000000000LL, 3000000000LL> b {i64{40000LL}}; + const auto c {a * b}; + const bounded_int<-3000000000LL, 3000000000LL> expected {i64{2000000000LL}}; + BOOST_TEST(c == expected); + + // Negative: -50000 * 40000 = -2000000000 + constexpr bounded_int<-3000000000LL, 3000000000LL> d {i64{-50000LL}}; + constexpr bounded_int<-3000000000LL, 3000000000LL> e {i64{40000LL}}; + const auto f {d * e}; + const bounded_int<-3000000000LL, 3000000000LL> expected_f {i64{-2000000000LL}}; + BOOST_TEST(f == expected_f); +} + +void test_i64_out_of_range_multiplication() +{ + // 100000 * 40000 = 4000000000 > 3000000000 -> domain_error + constexpr bounded_int<-3000000000LL, 3000000000LL> a {i64{100000LL}}; + constexpr bounded_int<-3000000000LL, 3000000000LL> b {i64{40000LL}}; + BOOST_TEST_THROWS(a * b, std::domain_error); + + // -100000 * 40000 = -4000000000 < -3000000000 -> domain_error + constexpr bounded_int<-3000000000LL, 3000000000LL> c {i64{-100000LL}}; + constexpr bounded_int<-3000000000LL, 3000000000LL> d {i64{40000LL}}; + BOOST_TEST_THROWS(c * d, std::domain_error); +} + +// ----------------------------------------------- +// Compound assignment *= +// ----------------------------------------------- + +void test_compound_multiplication() +{ + // i8 basis + bounded_int<-100, 100> a {i8{5}}; + a *= bounded_int<-100, 100>{i8{10}}; + const bounded_int<-100, 100> expected_a {i8{50}}; + BOOST_TEST(a == expected_a); + + // i16 basis + bounded_int<-1000, 1000> b {i16{25}}; + b *= bounded_int<-1000, 1000>{i16{30}}; + const bounded_int<-1000, 1000> expected_b {i16{750}}; + BOOST_TEST(b == expected_b); + + // i32 basis + bounded_int<-100000, 100000> c {i32{200}}; + c *= bounded_int<-100000, 100000>{i32{300}}; + const bounded_int<-100000, 100000> expected_c {i32{60000}}; + BOOST_TEST(c == expected_c); + + // i64 basis + bounded_int<-3000000000LL, 3000000000LL> d {i64{50000LL}}; + d *= bounded_int<-3000000000LL, 3000000000LL>{i64{40000LL}}; + const bounded_int<-3000000000LL, 3000000000LL> expected_d {i64{2000000000LL}}; + BOOST_TEST(d == expected_d); + + // Compound assignment that throws (11 * 10 = 110 > 100, fits in i8 but exceeds bounds) + bounded_int<-100, 100> e {i8{11}}; + using bi100 = bounded_int<-100, 100>; + BOOST_TEST_THROWS(e *= bi100{i8{10}}, std::domain_error); +} + +// ----------------------------------------------- +// Constexpr multiplication +// ----------------------------------------------- + +void test_constexpr_multiplication() +{ + constexpr bounded_int<-100, 100> a {i8{5}}; + constexpr bounded_int<-100, 100> b {i8{10}}; + constexpr auto c {a * b}; + constexpr bounded_int<-100, 100> expected {i8{50}}; + static_assert(c == expected); + + constexpr bounded_int<-1000, 1000> d {i16{-25}}; + constexpr bounded_int<-1000, 1000> e {i16{30}}; + constexpr auto f {d * e}; + constexpr bounded_int<-1000, 1000> expected_f {i16{-750}}; + static_assert(f == expected_f); + + constexpr bounded_int<-100000, 100000> g {i32{200}}; + constexpr bounded_int<-100000, 100000> h {i32{-300}}; + constexpr auto i {g * h}; + constexpr bounded_int<-100000, 100000> expected_i {i32{-60000}}; + static_assert(i == expected_i); +} + +int main() +{ + test_i8_valid_multiplication(); + test_i8_out_of_range_positive(); + test_i8_out_of_range_negative(); + test_i8_underlying_overflow(); + + test_i16_valid_multiplication(); + test_i16_out_of_range_multiplication(); + + test_i32_valid_multiplication(); + test_i32_out_of_range_multiplication(); + + test_i64_valid_multiplication(); + test_i64_out_of_range_multiplication(); + + test_compound_multiplication(); + test_constexpr_multiplication(); + + return boost::report_errors(); +} diff --git a/test/test_signed_bounded_subtraction.cpp b/test/test_signed_bounded_subtraction.cpp new file mode 100644 index 0000000..e671876 --- /dev/null +++ b/test/test_signed_bounded_subtraction.cpp @@ -0,0 +1,294 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-result" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-result" +#elif defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable: 4834) +#endif + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include +#include + +#endif + +#include +#include + +using namespace boost::safe_numbers; + +// ----------------------------------------------- +// i8 basis: bounded_int<-100, 100> +// ----------------------------------------------- + +void test_i8_valid_subtraction() +{ + // Basic subtraction: 50 - 30 = 20 + constexpr bounded_int<-100, 100> a {i8{50}}; + constexpr bounded_int<-100, 100> b {i8{30}}; + const auto c {a - b}; + const bounded_int<-100, 100> expected {i8{20}}; + BOOST_TEST(c == expected); + + // Subtracting zero + constexpr bounded_int<-100, 100> zero {i8{0}}; + const auto d {a - zero}; + BOOST_TEST(d == a); + + // Positive - negative: 30 - (-20) = 50 + constexpr bounded_int<-100, 100> e {i8{30}}; + constexpr bounded_int<-100, 100> f {i8{-20}}; + const auto g {e - f}; + const bounded_int<-100, 100> expected_g {i8{50}}; + BOOST_TEST(g == expected_g); + + // Negative result: 20 - 50 = -30 + constexpr bounded_int<-100, 100> h {i8{20}}; + constexpr bounded_int<-100, 100> i {i8{50}}; + const auto j {h - i}; + const bounded_int<-100, 100> expected_j {i8{-30}}; + BOOST_TEST(j == expected_j); + + // Min boundary: -50 - 50 = -100 + constexpr bounded_int<-100, 100> k {i8{-50}}; + constexpr bounded_int<-100, 100> l {i8{50}}; + const auto m {k - l}; + const bounded_int<-100, 100> expected_m {i8{-100}}; + BOOST_TEST(m == expected_m); + + // Max boundary: 50 - (-50) = 100 + constexpr bounded_int<-100, 100> n {i8{50}}; + constexpr bounded_int<-100, 100> o {i8{-50}}; + const auto p {n - o}; + const bounded_int<-100, 100> expected_p {i8{100}}; + BOOST_TEST(p == expected_p); +} + +void test_i8_out_of_range_positive() +{ + // 90 - (-20) = 110 > 100 -> domain_error + constexpr bounded_int<-100, 100> a {i8{90}}; + constexpr bounded_int<-100, 100> b {i8{-20}}; + BOOST_TEST_THROWS(a - b, std::domain_error); + + // Just one over: 51 - (-50) = 101 > 100 + constexpr bounded_int<-100, 100> c {i8{51}}; + constexpr bounded_int<-100, 100> d {i8{-50}}; + BOOST_TEST_THROWS(c - d, std::domain_error); +} + +void test_i8_out_of_range_negative() +{ + // -90 - 20 = -110 < -100 -> domain_error + constexpr bounded_int<-100, 100> a {i8{-90}}; + constexpr bounded_int<-100, 100> b {i8{20}}; + BOOST_TEST_THROWS(a - b, std::domain_error); + + // Just one below: -51 - 50 = -101 < -100 + constexpr bounded_int<-100, 100> c {i8{-51}}; + constexpr bounded_int<-100, 100> d {i8{50}}; + BOOST_TEST_THROWS(c - d, std::domain_error); +} + +void test_i8_underlying_overflow() +{ + // -128 - 127 would underflow the underlying i8 type + constexpr bounded_int<-128, 127> a {i8{-128}}; + constexpr bounded_int<-128, 127> b {i8{127}}; + BOOST_TEST_THROWS(a - b, std::underflow_error); + + // 127 - (-127) = 254, overflows underlying i8 + constexpr bounded_int<-128, 127> c {i8{127}}; + constexpr bounded_int<-128, 127> d {i8{-127}}; + BOOST_TEST_THROWS(c - d, std::overflow_error); +} + +// ----------------------------------------------- +// i16 basis: bounded_int<-1000, 1000> +// ----------------------------------------------- + +void test_i16_valid_subtraction() +{ + constexpr bounded_int<-1000, 1000> a {i16{500}}; + constexpr bounded_int<-1000, 1000> b {i16{300}}; + const auto c {a - b}; + const bounded_int<-1000, 1000> expected {i16{200}}; + BOOST_TEST(c == expected); + + // Negative result: -300 - 400 = -700 + constexpr bounded_int<-1000, 1000> d {i16{-300}}; + constexpr bounded_int<-1000, 1000> e {i16{400}}; + const auto f {d - e}; + const bounded_int<-1000, 1000> expected_f {i16{-700}}; + BOOST_TEST(f == expected_f); +} + +void test_i16_out_of_range_subtraction() +{ + // 900 - (-200) = 1100 > 1000 -> domain_error + constexpr bounded_int<-1000, 1000> a {i16{900}}; + constexpr bounded_int<-1000, 1000> b {i16{-200}}; + BOOST_TEST_THROWS(a - b, std::domain_error); + + // -900 - 200 = -1100 < -1000 -> domain_error + constexpr bounded_int<-1000, 1000> c {i16{-900}}; + constexpr bounded_int<-1000, 1000> d {i16{200}}; + BOOST_TEST_THROWS(c - d, std::domain_error); +} + +// ----------------------------------------------- +// i32 basis: bounded_int<-100000, 100000> +// ----------------------------------------------- + +void test_i32_valid_subtraction() +{ + constexpr bounded_int<-100000, 100000> a {i32{70000}}; + constexpr bounded_int<-100000, 100000> b {i32{30000}}; + const auto c {a - b}; + const bounded_int<-100000, 100000> expected {i32{40000}}; + BOOST_TEST(c == expected); + + // Subtracting a negative: 50000 - (-30000) = 80000 + constexpr bounded_int<-100000, 100000> d {i32{50000}}; + constexpr bounded_int<-100000, 100000> e {i32{-30000}}; + const auto f {d - e}; + const bounded_int<-100000, 100000> expected_f {i32{80000}}; + BOOST_TEST(f == expected_f); +} + +void test_i32_out_of_range_subtraction() +{ + // 60000 - (-60000) = 120000 > 100000 -> domain_error + constexpr bounded_int<-100000, 100000> a {i32{60000}}; + constexpr bounded_int<-100000, 100000> b {i32{-60000}}; + BOOST_TEST_THROWS(a - b, std::domain_error); + + // -60000 - 60000 = -120000 < -100000 -> domain_error + constexpr bounded_int<-100000, 100000> c {i32{-60000}}; + constexpr bounded_int<-100000, 100000> d {i32{60000}}; + BOOST_TEST_THROWS(c - d, std::domain_error); +} + +// ----------------------------------------------- +// i64 basis: bounded_int<-3000000000LL, 3000000000LL> +// ----------------------------------------------- + +void test_i64_valid_subtraction() +{ + constexpr bounded_int<-3000000000LL, 3000000000LL> a {i64{2000000000LL}}; + constexpr bounded_int<-3000000000LL, 3000000000LL> b {i64{500000000LL}}; + const auto c {a - b}; + const bounded_int<-3000000000LL, 3000000000LL> expected {i64{1500000000LL}}; + BOOST_TEST(c == expected); +} + +void test_i64_out_of_range_subtraction() +{ + // 2000000000 - (-2000000000) = 4000000000 > 3000000000 -> domain_error + constexpr bounded_int<-3000000000LL, 3000000000LL> a {i64{2000000000LL}}; + constexpr bounded_int<-3000000000LL, 3000000000LL> b {i64{-2000000000LL}}; + BOOST_TEST_THROWS(a - b, std::domain_error); + + // -2000000000 - 2000000000 = -4000000000 < -3000000000 -> domain_error + constexpr bounded_int<-3000000000LL, 3000000000LL> c {i64{-2000000000LL}}; + constexpr bounded_int<-3000000000LL, 3000000000LL> d {i64{2000000000LL}}; + BOOST_TEST_THROWS(c - d, std::domain_error); +} + +// ----------------------------------------------- +// Compound assignment -= +// ----------------------------------------------- + +void test_compound_subtraction() +{ + // i8 basis + bounded_int<-100, 100> a {i8{50}}; + a -= bounded_int<-100, 100>{i8{30}}; + const bounded_int<-100, 100> expected_a {i8{20}}; + BOOST_TEST(a == expected_a); + + // i16 basis + bounded_int<-1000, 1000> b {i16{500}}; + b -= bounded_int<-1000, 1000>{i16{300}}; + const bounded_int<-1000, 1000> expected_b {i16{200}}; + BOOST_TEST(b == expected_b); + + // i32 basis + bounded_int<-100000, 100000> c {i32{70000}}; + c -= bounded_int<-100000, 100000>{i32{30000}}; + const bounded_int<-100000, 100000> expected_c {i32{40000}}; + BOOST_TEST(c == expected_c); + + // i64 basis + bounded_int<-3000000000LL, 3000000000LL> d {i64{2000000000LL}}; + d -= bounded_int<-3000000000LL, 3000000000LL>{i64{500000000LL}}; + const bounded_int<-3000000000LL, 3000000000LL> expected_d {i64{1500000000LL}}; + BOOST_TEST(d == expected_d); + + // Compound assignment that throws + bounded_int<-100, 100> e {i8{-90}}; + using bi100 = bounded_int<-100, 100>; + BOOST_TEST_THROWS(e -= bi100{i8{20}}, std::domain_error); +} + +// ----------------------------------------------- +// Constexpr subtraction +// ----------------------------------------------- + +void test_constexpr_subtraction() +{ + constexpr bounded_int<-100, 100> a {i8{50}}; + constexpr bounded_int<-100, 100> b {i8{30}}; + constexpr auto c {a - b}; + constexpr bounded_int<-100, 100> expected {i8{20}}; + static_assert(c == expected); + + constexpr bounded_int<-1000, 1000> d {i16{500}}; + constexpr bounded_int<-1000, 1000> e {i16{800}}; + constexpr auto f {d - e}; + constexpr bounded_int<-1000, 1000> expected_f {i16{-300}}; + static_assert(f == expected_f); + + constexpr bounded_int<-100000, 100000> g {i32{0}}; + constexpr bounded_int<-100000, 100000> h {i32{50000}}; + constexpr auto i {g - h}; + constexpr bounded_int<-100000, 100000> expected_i {i32{-50000}}; + static_assert(i == expected_i); +} + +int main() +{ + test_i8_valid_subtraction(); + test_i8_out_of_range_positive(); + test_i8_out_of_range_negative(); + test_i8_underlying_overflow(); + + test_i16_valid_subtraction(); + test_i16_out_of_range_subtraction(); + + test_i32_valid_subtraction(); + test_i32_out_of_range_subtraction(); + + test_i64_valid_subtraction(); + test_i64_out_of_range_subtraction(); + + test_compound_subtraction(); + test_constexpr_subtraction(); + + return boost::report_errors(); +} diff --git a/test/test_signed_bounded_unary.cpp b/test/test_signed_bounded_unary.cpp new file mode 100644 index 0000000..384ca9f --- /dev/null +++ b/test/test_signed_bounded_unary.cpp @@ -0,0 +1,186 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +// Ignore [[nodiscard]] on the tests that we know are going to throw +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-result" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-result" +#elif defined(_MSC_VER) +# pragma warning (push) +# pragma warning (disable: 4834) +#endif + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include +#include + +#endif + +#include +#include + +using namespace boost::safe_numbers; + +// ----------------------------------------------- +// Unary plus +// ----------------------------------------------- + +void test_unary_plus() +{ + constexpr bounded_int<-100, 100> a {i8{42}}; + const auto b {+a}; + const bounded_int<-100, 100> expected {i8{42}}; + BOOST_TEST(b == expected); +} + +// ----------------------------------------------- +// Unary negation +// ----------------------------------------------- + +void test_negation_positive() +{ + constexpr bounded_int<-100, 100> a {i8{42}}; + const auto b {-a}; + const bounded_int<-100, 100> expected {i8{-42}}; + BOOST_TEST(b == expected); +} + +void test_negation_negative() +{ + constexpr bounded_int<-100, 100> a {i8{-42}}; + const auto b {-a}; + const bounded_int<-100, 100> expected {i8{42}}; + BOOST_TEST(b == expected); +} + +void test_negation_zero() +{ + constexpr bounded_int<-100, 100> a {i8{0}}; + const auto b {-a}; + const bounded_int<-100, 100> expected {i8{0}}; + BOOST_TEST(b == expected); +} + +void test_negation_out_of_range() +{ + // -(-10) = 10, but max is 5, so the constructor rejects it + constexpr bounded_int<-10, 5> a {i8{-10}}; + BOOST_TEST_THROWS(-a, std::domain_error); +} + +void test_negation_type_min_overflow() +{ + // -(-128) overflows int8_t (result 128 > INT8_MAX) + constexpr bounded_int<-128, 127> a {i8{-128}}; + BOOST_TEST_THROWS(-a, std::overflow_error); +} + +// ----------------------------------------------- +// Pre-increment +// ----------------------------------------------- + +void test_pre_increment_valid() +{ + bounded_int<-100, 100> a {i8{99}}; + ++a; + const bounded_int<-100, 100> expected {i8{100}}; + BOOST_TEST(a == expected); +} + +void test_pre_increment_at_max() +{ + bounded_int<-100, 100> a {i8{100}}; + BOOST_TEST_THROWS(++a, std::domain_error); +} + +// ----------------------------------------------- +// Pre-decrement +// ----------------------------------------------- + +void test_pre_decrement_valid() +{ + bounded_int<-100, 100> a {i8{-99}}; + --a; + const bounded_int<-100, 100> expected {i8{-100}}; + BOOST_TEST(a == expected); +} + +void test_pre_decrement_at_min() +{ + bounded_int<-100, 100> a {i8{-100}}; + BOOST_TEST_THROWS(--a, std::domain_error); +} + +// ----------------------------------------------- +// Post-increment +// ----------------------------------------------- + +void test_post_increment_valid() +{ + bounded_int<-100, 100> a {i8{50}}; + const auto old {a++}; + const bounded_int<-100, 100> expected_old {i8{50}}; + const bounded_int<-100, 100> expected_new {i8{51}}; + BOOST_TEST(old == expected_old); + BOOST_TEST(a == expected_new); +} + +void test_post_increment_at_max() +{ + bounded_int<-100, 100> a {i8{100}}; + BOOST_TEST_THROWS(a++, std::domain_error); +} + +// ----------------------------------------------- +// Post-decrement +// ----------------------------------------------- + +void test_post_decrement_valid() +{ + bounded_int<-100, 100> a {i8{-50}}; + const auto old {a--}; + const bounded_int<-100, 100> expected_old {i8{-50}}; + const bounded_int<-100, 100> expected_new {i8{-51}}; + BOOST_TEST(old == expected_old); + BOOST_TEST(a == expected_new); +} + +void test_post_decrement_at_min() +{ + bounded_int<-100, 100> a {i8{-100}}; + BOOST_TEST_THROWS(a--, std::domain_error); +} + +int main() +{ + test_unary_plus(); + test_negation_positive(); + test_negation_negative(); + test_negation_zero(); + test_negation_out_of_range(); + test_negation_type_min_overflow(); + + test_pre_increment_valid(); + test_pre_increment_at_max(); + test_pre_decrement_valid(); + test_pre_decrement_at_min(); + + test_post_increment_valid(); + test_post_increment_at_max(); + test_post_decrement_valid(); + test_post_decrement_at_min(); + + return boost::report_errors(); +}