diff --git a/doc/modules/ROOT/pages/examples.adoc b/doc/modules/ROOT/pages/examples.adoc index 26fbb82..9a3582b 100644 --- a/doc/modules/ROOT/pages/examples.adoc +++ b/doc/modules/ROOT/pages/examples.adoc @@ -511,6 +511,39 @@ numeric_limits::digits = 32 ---- ==== +[#examples_verified_mixed_ops] +== Verified Types: Mixed-Type Operations + +Verified types are compile-time constants, but they can participate in runtime expressions when mixed with their basis types. +The result is always the runtime (basis) type, treating the verified value as a read-only constant operand. +Arithmetic, comparisons, and bitwise operations are all supported. + +.This https://github.com/boostorg/safe_numbers/blob/develop/examples/verified_mixed_ops.cpp[example] demonstrates mixed-type operations between verified constants and runtime values. +==== +[source, c++] +---- +include::example$verified_mixed_ops.cpp[] +---- + +Output: +---- +Tax on 500 at 20%: 100 +500 - 50 discount = 450 + +500 < 1000: true +1000 > 500: true +500 == 1000: false + +0xABCD & 0xFF = 205 +0xFF | 0xABCD = 44031 +0xABCD << 4 = 703696 + +bounded 50 + 10 = 60 +bounded 50 - 10 = 40 +bounded 50 > 10: true +---- +==== + [#examples_verified_compile_fail] == Verified Types: Compile-Time Overflow Detection diff --git a/doc/modules/ROOT/pages/verified_integers.adoc b/doc/modules/ROOT/pages/verified_integers.adoc index 296a2ef..e42c3f8 100644 --- a/doc/modules/ROOT/pages/verified_integers.adoc +++ b/doc/modules/ROOT/pages/verified_integers.adoc @@ -203,8 +203,100 @@ consteval auto operator--(int) -> verified_type_basis; | Increment/decrement (`++`, `--`) | `consteval` | Yes | No | Conversion to `BasisType` / `underlying_type` | `constexpr` | Yes | Yes | Comparison (`<=>`, `==`, `<`, etc.) | `constexpr` | Yes | Yes +| Mixed-type arithmetic (verified op basis) | `constexpr` | Yes | Yes +| Mixed-type comparison (verified vs basis) | `constexpr` | Yes | Yes +| Mixed-type bitwise (verified op basis) | `constexpr` | Yes | Yes |=== +== Mixed-Type Operations + +Verified types can participate in runtime expressions when combined with their basis types. +The result is always the runtime (basis) type, effectively treating the verified value as a read-only constant operand. + +=== Arithmetic + +[source,c++] +---- +// verified op basis -> basis +template +constexpr auto operator+(const verified_type_basis lhs, + const VerifiedBasisType rhs) -> VerifiedBasisType; + +// basis op verified -> basis +template +constexpr auto operator+(const VerifiedBasisType lhs, + const verified_type_basis rhs) -> VerifiedBasisType; +---- + +All five arithmetic operators (`+`, `-`, `*`, `/`, `%`) are supported in both directions. +Cross-width operations (e.g., `verified_u32 + u64`) are allowed and follow the same rules as the basis types. + +=== Comparisons + +[source,c++] +---- +template +constexpr auto operator<=>(const verified_type_basis lhs, + const VerifiedBasisType rhs) -> std::strong_ordering; + +template +constexpr auto operator==(const verified_type_basis lhs, + const VerifiedBasisType rhs) -> bool; +---- + +Full three-way and equality comparisons are supported in both directions (verified vs basis, basis vs verified). + +=== Bitwise Operations + +[source,c++] +---- +template +constexpr auto operator&(const verified_type_basis lhs, + const VerifiedBasisType rhs) -> VerifiedBasisType; +---- + +Bitwise operators (`&`, `|`, `^`, `<<`, `>>`) are supported for non-bounded unsigned types in both directions. +Shift operators include overflow checking via `throw_exception` policy. + +=== Type Safety + +Mixed operations between verified types with *different* basis types produce clear `static_assert` errors: + +[source,c++] +---- +constexpr auto a = verified_u32{u32{10}}; +constexpr auto b = verified_u64{u64{20}}; + +auto c = a + b; // error: "Can not perform addition between verified types with different basis types" +auto d = (a == b); // error: "Can not compare verified types with different basis types" +---- + +.This https://github.com/boostorg/safe_numbers/blob/develop/examples/verified_mixed_ops.cpp[example] demonstrates mixed-type operations between verified constants and runtime values. +==== +[source, c++] +---- +include::example$verified_mixed_ops.cpp[] +---- + +Output: +---- +Tax on 500 at 20%: 100 +500 - 50 discount = 450 + +500 < 1000: true +1000 > 500: true +500 == 1000: false + +0xABCD & 0xFF = 205 +0xFF | 0xABCD = 44031 +0xABCD << 4 = 703696 + +bounded 50 + 10 = 60 +bounded 50 - 10 = 40 +bounded 50 > 10: true +---- +==== + == Integration with Other Features Verified types integrate with the library's other features: diff --git a/examples/verified_mixed_ops.cpp b/examples/verified_mixed_ops.cpp new file mode 100644 index 0000000..8c3dedb --- /dev/null +++ b/examples/verified_mixed_ops.cpp @@ -0,0 +1,71 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// +// Verified types are compile-time constants, but they can participate in +// runtime operations when mixed with their basis types. The result is +// always the runtime (basis) type, effectively treating the verified value +// as a read-only constant operand. + +#include +#include +#include + +int main() +{ + using namespace boost::safe_numbers; + + // A compile-time constant and a runtime value + constexpr auto tax_rate = verified_u32{u32{20}}; + auto price = u32{500}; + + // --- Arithmetic: verified op basis -> basis --- + const auto tax = tax_rate * price / u32{100}; + std::cout << "Tax on " << price << " at " << tax_rate << "%: " << tax << '\n'; + + // --- Arithmetic: basis op verified -> basis --- + constexpr auto discount = verified_u32{u32{50}}; + const auto discounted = price - discount; + std::cout << price << " - " << discount << " discount = " << discounted << '\n'; + + std::cout << '\n'; + + // --- Comparisons: verified vs basis --- + constexpr auto threshold = verified_u32{u32{1000}}; + std::cout << std::boolalpha; + std::cout << price << " < " << threshold << ": " << (price < threshold) << '\n'; + std::cout << threshold << " > " << price << ": " << (threshold > price) << '\n'; + std::cout << price << " == " << threshold << ": " << (price == threshold) << '\n'; + + std::cout << '\n'; + + // --- Bitwise operations: verified vs basis --- + constexpr auto mask = verified_u32{u32{0xFF}}; + auto value = u32{0xABCD}; + const auto masked = value & mask; + std::cout << "0xABCD & 0xFF = " << masked << '\n'; + + const auto combined = mask | value; + std::cout << "0xFF | 0xABCD = " << combined << '\n'; + + // Shift operations + constexpr auto shift = verified_u32{u32{4}}; + const auto shifted = value << shift; + std::cout << "0xABCD << 4 = " << shifted << '\n'; + + std::cout << '\n'; + + // --- Bounded types work too --- + constexpr auto bounded_offset = verified_bounded_integer<0u, 100u>{10u}; + auto bounded_val = bounded_uint<0u, 100u>{50u}; + const auto bounded_sum = bounded_val + bounded_offset; + std::cout << "bounded 50 + 10 = " << bounded_sum << '\n'; + + const auto bounded_diff = bounded_val - bounded_offset; + std::cout << "bounded 50 - 10 = " << bounded_diff << '\n'; + + // Bounded comparisons + std::cout << "bounded 50 > 10: " << (bounded_val > bounded_offset) << '\n'; + + return 0; +} diff --git a/include/boost/safe_numbers/bounded_integers.hpp b/include/boost/safe_numbers/bounded_integers.hpp index 64f784a..0da56c1 100644 --- a/include/boost/safe_numbers/bounded_integers.hpp +++ b/include/boost/safe_numbers/bounded_integers.hpp @@ -267,9 +267,38 @@ BOOST_SAFE_NUMBERS_DEFINE_MIXED_BOUNDED_UINT_OP("subtraction", operator-) BOOST_SAFE_NUMBERS_DEFINE_MIXED_BOUNDED_UINT_OP("multiplication", operator*) BOOST_SAFE_NUMBERS_DEFINE_MIXED_BOUNDED_UINT_OP("division", operator/) BOOST_SAFE_NUMBERS_DEFINE_MIXED_BOUNDED_UINT_OP("modulo", operator%) +BOOST_SAFE_NUMBERS_DEFINE_MIXED_BOUNDED_UINT_OP("and", operator&) +BOOST_SAFE_NUMBERS_DEFINE_MIXED_BOUNDED_UINT_OP("or", operator|) +BOOST_SAFE_NUMBERS_DEFINE_MIXED_BOUNDED_UINT_OP("xor", operator^) +BOOST_SAFE_NUMBERS_DEFINE_MIXED_BOUNDED_UINT_OP("right shift", operator>>) +BOOST_SAFE_NUMBERS_DEFINE_MIXED_BOUNDED_UINT_OP("left shift", operator<<) } // namespace boost::safe_numbers #undef BOOST_SAFE_NUMBERS_DEFINE_MIXED_BOUNDED_UINT_OP +#define BOOST_SAFE_NUMBERS_DEFINE_BITWISE_OP(OP_NAME, OP_SYMBOL) \ +template \ +constexpr auto OP_SYMBOL(const boost::safe_numbers::bounded_uint, \ + const boost::safe_numbers::bounded_uint) \ +{ \ + static_assert(boost::safe_numbers::detail::dependent_false>, \ + "Can not perform " OP_NAME "between bounded_uint types as the results would be non-sensical"); \ + \ + return boost::safe_numbers::bounded_uint( \ + typename boost::safe_numbers::bounded_uint::basis_type{0}); \ +} \ + +namespace boost::safe_numbers { + +BOOST_SAFE_NUMBERS_DEFINE_BITWISE_OP("and", operator&) +BOOST_SAFE_NUMBERS_DEFINE_BITWISE_OP("or", operator|) +BOOST_SAFE_NUMBERS_DEFINE_BITWISE_OP("xor", operator^) +BOOST_SAFE_NUMBERS_DEFINE_BITWISE_OP("right shift", operator>>) +BOOST_SAFE_NUMBERS_DEFINE_BITWISE_OP("left shift", operator<<) + +} + +#undef BOOST_SAFE_NUMBERS_DEFINE_BITWISE_OP + #endif // BOOST_SAFE_NUMBERS_BOUNDED_INTEGERS_HPP diff --git a/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp b/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp index 06c21d8..fce4d4b 100644 --- a/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp +++ b/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp @@ -2320,7 +2320,7 @@ template constexpr auto detail::unsigned_integer_basis::operator&=(const unsigned_integer_basis rhs) noexcept -> unsigned_integer_basis& { - *this = *this & rhs; + *this = boost::safe_numbers::operator&(*this, rhs); return *this; } @@ -2328,7 +2328,7 @@ template constexpr auto detail::unsigned_integer_basis::operator|=(const unsigned_integer_basis rhs) noexcept -> unsigned_integer_basis& { - *this = *this | rhs; + *this = boost::safe_numbers::operator|(*this, rhs); return *this; } @@ -2336,7 +2336,7 @@ template constexpr auto detail::unsigned_integer_basis::operator^=(const unsigned_integer_basis rhs) noexcept -> unsigned_integer_basis& { - *this = *this ^ rhs; + *this = boost::safe_numbers::operator^(*this, rhs); return *this; } diff --git a/include/boost/safe_numbers/detail/verified_type_basis.hpp b/include/boost/safe_numbers/detail/verified_type_basis.hpp index c951f10..39e226d 100644 --- a/include/boost/safe_numbers/detail/verified_type_basis.hpp +++ b/include/boost/safe_numbers/detail/verified_type_basis.hpp @@ -212,6 +212,164 @@ constexpr auto operator==(const verified_type_basis, return false; } +// At runtime, we want to enable mixed operations between verified and unverified values of the same width +// e.g., u32 + verified_u32 = u32 or u32 <=> verified_u32 +// These operations will always result in the runtime value + +#define BOOST_SAFE_NUMBERS_MIXED_VERIFIED_OPERATOR(op) \ +template \ +constexpr auto operator op(const verified_type_basis lhs, \ + const VerifiedBasisType rhs) -> VerifiedBasisType \ +{ \ + return static_cast(lhs) op rhs; \ +} \ + \ +template \ +constexpr auto operator op(const VerifiedBasisType lhs, \ + const verified_type_basis rhs) -> VerifiedBasisType \ +{ \ + return lhs op static_cast(rhs); \ +} \ + \ +template \ +constexpr auto operator op(const verified_type_basis lhs, \ + const OtherBasis rhs) -> VerifiedBasisType \ +{ \ + return static_cast(lhs) op rhs; \ +} \ + \ +template \ +constexpr auto operator op(const OtherBasis lhs, \ + const verified_type_basis rhs) -> VerifiedBasisType \ +{ \ + return static_cast(lhs) op rhs; \ +} + +BOOST_SAFE_NUMBERS_MIXED_VERIFIED_OPERATOR(+) +BOOST_SAFE_NUMBERS_MIXED_VERIFIED_OPERATOR(-) +BOOST_SAFE_NUMBERS_MIXED_VERIFIED_OPERATOR(*) +BOOST_SAFE_NUMBERS_MIXED_VERIFIED_OPERATOR(/) +BOOST_SAFE_NUMBERS_MIXED_VERIFIED_OPERATOR(%) + +#undef BOOST_SAFE_NUMBERS_MIXED_VERIFIED_OPERATOR + +// Bitwise mixed operators (&, |, ^) need separate treatment because the native +// unsigned_integer_basis bitwise operators live in boost::safe_numbers (not detail), +// so ADL cannot find them from within detail. We operate on raw values directly. + +#define BOOST_SAFE_NUMBERS_MIXED_VERIFIED_BITWISE_OPERATOR(op) \ +template \ +constexpr auto operator op(const verified_type_basis lhs, \ + const VerifiedBasisType rhs) -> VerifiedBasisType \ +{ \ + using raw = underlying_type_t; \ + return VerifiedBasisType{static_cast( \ + raw_value(static_cast(lhs)) op raw_value(rhs))}; \ +} \ + \ +template \ +constexpr auto operator op(const VerifiedBasisType lhs, \ + const verified_type_basis rhs) -> VerifiedBasisType \ +{ \ + using raw = underlying_type_t; \ + return VerifiedBasisType{static_cast( \ + raw_value(lhs) op raw_value(static_cast(rhs)))}; \ +} + +BOOST_SAFE_NUMBERS_MIXED_VERIFIED_BITWISE_OPERATOR(&) +BOOST_SAFE_NUMBERS_MIXED_VERIFIED_BITWISE_OPERATOR(|) +BOOST_SAFE_NUMBERS_MIXED_VERIFIED_BITWISE_OPERATOR(^) + +#undef BOOST_SAFE_NUMBERS_MIXED_VERIFIED_BITWISE_OPERATOR + +// Shift mixed operators (<<, >>) call shl_impl/shr_impl directly (in detail namespace) + +template +constexpr auto operator<<(const verified_type_basis lhs, + const VerifiedBasisType rhs) -> VerifiedBasisType +{ + return shl_impl(static_cast(lhs), rhs); +} + +template +constexpr auto operator<<(const VerifiedBasisType lhs, + const verified_type_basis rhs) -> VerifiedBasisType +{ + return shl_impl(lhs, static_cast(rhs)); +} + +template +constexpr auto operator>>(const verified_type_basis lhs, + const VerifiedBasisType rhs) -> VerifiedBasisType +{ + return shr_impl(static_cast(lhs), rhs); +} + +template +constexpr auto operator>>(const VerifiedBasisType lhs, + const verified_type_basis rhs) -> VerifiedBasisType +{ + return shr_impl(lhs, static_cast(rhs)); +} + +// Separate implementations for the comparisons since we can't shoehorn them into the macros above + +template +constexpr auto operator<=>(const verified_type_basis lhs, + const VerifiedBasisType rhs) -> std::strong_ordering +{ + return static_cast(lhs) <=> rhs; +} + +template +constexpr auto operator==(const verified_type_basis lhs, + const VerifiedBasisType rhs) -> bool +{ + return static_cast(lhs) == rhs; +} + +template +constexpr auto operator<=>(const VerifiedBasisType lhs, + const verified_type_basis rhs) -> std::strong_ordering +{ + return lhs <=> static_cast(rhs); +} + +template +constexpr auto operator==(const VerifiedBasisType lhs, + const verified_type_basis rhs) -> bool +{ + return lhs == static_cast(rhs); +} + +template +constexpr auto operator<=>(const verified_type_basis lhs, + const OtherBasis rhs) -> std::strong_ordering +{ + return static_cast(lhs) <=> rhs; +} + +template +constexpr auto operator==(const verified_type_basis lhs, + const OtherBasis rhs) -> bool +{ + return static_cast(lhs) == rhs; +} + +template +constexpr auto operator<=>(const OtherBasis lhs, + const verified_type_basis rhs) -> std::strong_ordering +{ + return lhs <=> static_cast(rhs); +} + +template +constexpr auto operator==(const OtherBasis lhs, + const verified_type_basis rhs) -> bool +{ + return lhs == static_cast(rhs); +} + } // namespace boost::safe_numbers::detail #endif // BOOST_SAFE_NUMBERS_VERIFIED_INTEGER_BASIS_HPP diff --git a/test/Jamfile b/test/Jamfile index 1a46004..7825034 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -118,6 +118,10 @@ compile-fail compile_fail_bounded_mixed_ops.cpp ; compile-fail compile_fail_bounded_bool_bounds.cpp ; run test_verified_integers.cpp ; +run test_verified_mixed_ops.cpp ; +compile-fail compile_fail_verified_mixed_width_add.cpp ; +compile-fail compile_fail_verified_cross_type_add.cpp ; +compile-fail compile_fail_verified_cross_type_comparison.cpp ; run test_verified_streaming.cpp ; run test_verified_bit.cpp ; run test_verified_charconv.cpp ; @@ -155,4 +159,5 @@ run ../examples/bitwise_ops.cpp ; run ../examples/verified_construction.cpp ; run ../examples/verified_arithmetic.cpp ; run ../examples/verified_runtime_usage.cpp ; +run ../examples/verified_mixed_ops.cpp ; compile-fail ../examples/compile_fail_verified_overflow.cpp ; diff --git a/test/compile_fail_verified_cross_type_add.cpp b/test/compile_fail_verified_cross_type_add.cpp new file mode 100644 index 0000000..a37e384 --- /dev/null +++ b/test/compile_fail_verified_cross_type_add.cpp @@ -0,0 +1,19 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +using namespace boost::safe_numbers; + +int main() +{ + constexpr auto a = verified_u32{u32{10}}; + constexpr auto b = verified_u64{u64{20}}; + + // This should fail to compile: can not perform addition between + // verified types with different basis types + auto c = a + b; + + return 0; +} diff --git a/test/compile_fail_verified_cross_type_comparison.cpp b/test/compile_fail_verified_cross_type_comparison.cpp new file mode 100644 index 0000000..17d05d4 --- /dev/null +++ b/test/compile_fail_verified_cross_type_comparison.cpp @@ -0,0 +1,19 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +using namespace boost::safe_numbers; + +int main() +{ + constexpr auto a = verified_u32{u32{10}}; + constexpr auto b = verified_u64{u64{10}}; + + // This should fail to compile: can not compare verified types + // with different basis types + auto result = (a == b); + + return 0; +} diff --git a/test/compile_fail_verified_mixed_width_add.cpp b/test/compile_fail_verified_mixed_width_add.cpp new file mode 100644 index 0000000..2b18b61 --- /dev/null +++ b/test/compile_fail_verified_mixed_width_add.cpp @@ -0,0 +1,18 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +using namespace boost::safe_numbers; + +int main() +{ + constexpr auto a = verified_u32{u32{10}}; + const auto b = u64{20}; + + // This should fail to compile: verified_u32 + u64 is a mixed-width operation + auto c = a + b; + + return 0; +} diff --git a/test/test_verified_mixed_ops.cpp b/test/test_verified_mixed_ops.cpp new file mode 100644 index 0000000..affb1f3 --- /dev/null +++ b/test/test_verified_mixed_ops.cpp @@ -0,0 +1,565 @@ +// 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 +#include +#include + +#endif + +#include + +using namespace boost::safe_numbers; + +// ============================================================================= +// Mixed arithmetic: verified_uN op uN -> uN +// ============================================================================= + +template +void test_verified_op_basis_arithmetic() +{ + constexpr auto v10 = VerifiedT{BasisT{10}}; + constexpr auto v20 = VerifiedT{BasisT{20}}; + constexpr auto v5 = VerifiedT{BasisT{5}}; + constexpr auto v17 = VerifiedT{BasisT{17}}; + + const auto b3 = BasisT{3}; + const auto b5 = BasisT{5}; + const auto b7 = BasisT{7}; + + // verified + basis -> basis + BOOST_TEST((v10 + b7) == BasisT{17}); + + // verified - basis -> basis + BOOST_TEST((v20 - b7) == BasisT{13}); + + // verified * basis -> basis + BOOST_TEST((v5 * b3) == BasisT{15}); + + // verified / basis -> basis + BOOST_TEST((v20 / b5) == BasisT{4}); + + // verified % basis -> basis + BOOST_TEST((v17 % b5) == BasisT{2}); +} + +// ============================================================================= +// Mixed arithmetic: uN op verified_uN -> uN +// ============================================================================= + +template +void test_basis_op_verified_arithmetic() +{ + constexpr auto v3 = VerifiedT{BasisT{3}}; + constexpr auto v5 = VerifiedT{BasisT{5}}; + constexpr auto v7 = VerifiedT{BasisT{7}}; + + const auto b10 = BasisT{10}; + const auto b17 = BasisT{17}; + const auto b20 = BasisT{20}; + + // basis + verified -> basis + BOOST_TEST((b10 + v7) == BasisT{17}); + + // basis - verified -> basis + BOOST_TEST((b20 - v7) == BasisT{13}); + + // basis * verified -> basis + BOOST_TEST((b10 * v3) == BasisT{30}); + + // basis / verified -> basis + BOOST_TEST((b20 / v5) == BasisT{4}); + + // basis % verified -> basis + BOOST_TEST((b17 % v5) == BasisT{2}); +} + +// ============================================================================= +// Asymmetric operand tests (ensures both operands are used correctly) +// ============================================================================= + +template +void test_asymmetric_operands() +{ + // Use values where a op b != b op a for non-commutative ops (-, /, %) + constexpr auto v30 = VerifiedT{BasisT{30}}; + constexpr auto v7 = VerifiedT{BasisT{7}}; + + const auto b10 = BasisT{10}; + + // verified - basis: 30 - 10 = 20 + BOOST_TEST((v30 - b10) == BasisT{20}); + + // basis - verified: 10 - 7 = 3 + BOOST_TEST((b10 - v7) == BasisT{3}); + + // verified / basis: 30 / 10 = 3 + BOOST_TEST((v30 / b10) == BasisT{3}); + + // basis / verified: 10 / 7 = 1 + BOOST_TEST((b10 / v7) == BasisT{1}); + + // verified % basis: 30 % 10 = 0 + BOOST_TEST((v30 % b10) == BasisT{0}); + + // basis % verified: 10 % 7 = 3 + BOOST_TEST((b10 % v7) == BasisT{3}); +} + +// ============================================================================= +// Mixed comparisons: verified_uN vs uN +// ============================================================================= + +template +void test_mixed_comparisons() +{ + constexpr auto v10 = VerifiedT{BasisT{10}}; + constexpr auto v20 = VerifiedT{BasisT{20}}; + + const auto b10 = BasisT{10}; + const auto b20 = BasisT{20}; + const auto b5 = BasisT{5}; + + // verified == basis + BOOST_TEST(v10 == b10); + BOOST_TEST(!(v10 == b20)); + + // verified != basis + BOOST_TEST(v10 != b20); + BOOST_TEST(!(v10 != b10)); + + // verified < basis + BOOST_TEST(v10 < b20); + BOOST_TEST(!(v20 < b10)); + + // verified <= basis + BOOST_TEST(v10 <= b10); + BOOST_TEST(v10 <= b20); + BOOST_TEST(!(v20 <= b5)); + + // verified > basis + BOOST_TEST(v20 > b10); + BOOST_TEST(!(v10 > b20)); + + // verified >= basis + BOOST_TEST(v10 >= b10); + BOOST_TEST(v20 >= b10); + BOOST_TEST(!(v10 >= b20)); + + // verified <=> basis + BOOST_TEST((v10 <=> b10) == std::strong_ordering::equal); + BOOST_TEST((v10 <=> b20) == std::strong_ordering::less); + BOOST_TEST((v20 <=> b10) == std::strong_ordering::greater); + + // basis == verified + BOOST_TEST(b10 == v10); + BOOST_TEST(!(b20 == v10)); + + // basis != verified + BOOST_TEST(b20 != v10); + BOOST_TEST(!(b10 != v10)); + + // basis < verified + BOOST_TEST(b10 < v20); + BOOST_TEST(!(b20 < v10)); + + // basis <= verified + BOOST_TEST(b10 <= v10); + BOOST_TEST(b10 <= v20); + + // basis > verified + BOOST_TEST(b20 > v10); + BOOST_TEST(!(b10 > v20)); + + // basis >= verified + BOOST_TEST(b10 >= v10); + BOOST_TEST(b20 >= v10); + + // basis <=> verified + BOOST_TEST((b10 <=> v10) == std::strong_ordering::equal); + BOOST_TEST((b10 <=> v20) == std::strong_ordering::less); + BOOST_TEST((b20 <=> v10) == std::strong_ordering::greater); +} + +// ============================================================================= +// Mixed ops with bounded_uint types +// ============================================================================= + +void test_bounded_mixed_arithmetic() +{ + using bounded_t = bounded_uint<0u, 100u>; + using verified_bounded_t = verified_bounded_integer<0u, 100u>; + + constexpr auto v10 = verified_bounded_t{bounded_t{u8{10}}}; + constexpr auto v20 = verified_bounded_t{bounded_t{u8{20}}}; + constexpr auto v5 = verified_bounded_t{bounded_t{u8{5}}}; + constexpr auto v7 = verified_bounded_t{bounded_t{u8{7}}}; + constexpr auto v17 = verified_bounded_t{bounded_t{u8{17}}}; + + const auto b3 = bounded_t{u8{3}}; + const auto b5 = bounded_t{u8{5}}; + const auto b7 = bounded_t{u8{7}}; + const auto b10 = bounded_t{u8{10}}; + + // verified + basis -> basis + BOOST_TEST((v10 + b7) == bounded_t{u8{17}}); + + // basis + verified -> basis + BOOST_TEST((b10 + v7) == bounded_t{u8{17}}); + + // verified - basis -> basis + BOOST_TEST((v20 - b7) == bounded_t{u8{13}}); + + // basis - verified -> basis + BOOST_TEST((b10 - v5) == bounded_t{u8{5}}); + + // verified * basis -> basis + BOOST_TEST((v5 * b3) == bounded_t{u8{15}}); + + // basis * verified -> basis + BOOST_TEST((b3 * v5) == bounded_t{u8{15}}); + + // verified / basis -> basis + BOOST_TEST((v20 / b5) == bounded_t{u8{4}}); + + // basis / verified -> basis + BOOST_TEST((b10 / v5) == bounded_t{u8{2}}); + + // verified % basis -> basis + BOOST_TEST((v17 % b5) == bounded_t{u8{2}}); + + // basis % verified -> basis + BOOST_TEST((b10 % v5) == bounded_t{u8{0}}); +} + +void test_bounded_mixed_comparisons() +{ + using bounded_t = bounded_uint<0u, 100u>; + using verified_bounded_t = verified_bounded_integer<0u, 100u>; + + constexpr auto v10 = verified_bounded_t{bounded_t{u8{10}}}; + constexpr auto v20 = verified_bounded_t{bounded_t{u8{20}}}; + + const auto b10 = bounded_t{u8{10}}; + const auto b20 = bounded_t{u8{20}}; + + // verified == basis + BOOST_TEST(v10 == b10); + BOOST_TEST(!(v10 == b20)); + + // basis == verified + BOOST_TEST(b10 == v10); + BOOST_TEST(!(b20 == v10)); + + // verified != basis + BOOST_TEST(v10 != b20); + BOOST_TEST(!(v10 != b10)); + + // verified < basis + BOOST_TEST(v10 < b20); + BOOST_TEST(!(v20 < b10)); + + // verified > basis + BOOST_TEST(v20 > b10); + BOOST_TEST(!(v10 > b20)); + + // verified <=> basis + BOOST_TEST((v10 <=> b10) == std::strong_ordering::equal); + BOOST_TEST((v10 <=> b20) == std::strong_ordering::less); + BOOST_TEST((v20 <=> b10) == std::strong_ordering::greater); + + // basis <=> verified + BOOST_TEST((b10 <=> v10) == std::strong_ordering::equal); + BOOST_TEST((b10 <=> v20) == std::strong_ordering::less); + BOOST_TEST((b20 <=> v10) == std::strong_ordering::greater); +} + +void test_bounded_asymmetric_operands() +{ + using bounded_t = bounded_uint<0u, 100u>; + using verified_bounded_t = verified_bounded_integer<0u, 100u>; + + constexpr auto v30 = verified_bounded_t{bounded_t{u8{30}}}; + constexpr auto v7 = verified_bounded_t{bounded_t{u8{7}}}; + + const auto b10 = bounded_t{u8{10}}; + + // verified - basis: 30 - 10 = 20 + BOOST_TEST((v30 - b10) == bounded_t{u8{20}}); + + // basis - verified: 10 - 7 = 3 + BOOST_TEST((b10 - v7) == bounded_t{u8{3}}); + + // verified / basis: 30 / 10 = 3 + BOOST_TEST((v30 / b10) == bounded_t{u8{3}}); + + // basis / verified: 10 / 7 = 1 + BOOST_TEST((b10 / v7) == bounded_t{u8{1}}); + + // verified % basis: 30 % 10 = 0 + BOOST_TEST((v30 % b10) == bounded_t{u8{0}}); + + // basis % verified: 10 % 7 = 3 + BOOST_TEST((b10 % v7) == bounded_t{u8{3}}); +} + +// ============================================================================= +// Wider bounded range tests (u16, u32 basis) +// ============================================================================= + +void test_bounded_u16_mixed_ops() +{ + using bounded_t = bounded_uint<0u, 1000u>; + using verified_bounded_t = verified_bounded_integer<0u, 1000u>; + + constexpr auto v100 = verified_bounded_t{bounded_t{u16{100}}}; + constexpr auto v200 = verified_bounded_t{bounded_t{u16{200}}}; + + const auto b50 = bounded_t{u16{50}}; + const auto b100 = bounded_t{u16{100}}; + + // Arithmetic + BOOST_TEST((v100 + b50) == bounded_t{u16{150}}); + BOOST_TEST((b50 + v100) == bounded_t{u16{150}}); + BOOST_TEST((v200 - b50) == bounded_t{u16{150}}); + BOOST_TEST((b100 - v100) == bounded_t{u16{0}}); + BOOST_TEST((v200 / b100) == bounded_t{u16{2}}); + BOOST_TEST((b100 % v200) == bounded_t{u16{100}}); + + // Comparisons + BOOST_TEST(v100 == b100); + BOOST_TEST(v100 < bounded_t{u16{200}}); + BOOST_TEST((v200 <=> b100) == std::strong_ordering::greater); +} + +void test_bounded_u32_mixed_ops() +{ + using bounded_t = bounded_uint<0u, 100000u>; + using verified_bounded_t = verified_bounded_integer<0u, 100000u>; + + constexpr auto v1000 = verified_bounded_t{bounded_t{u32{1000}}}; + constexpr auto v5000 = verified_bounded_t{bounded_t{u32{5000}}}; + + const auto b500 = bounded_t{u32{500}}; + const auto b1000 = bounded_t{u32{1000}}; + + // Arithmetic + BOOST_TEST((v1000 + b500) == bounded_t{u32{1500}}); + BOOST_TEST((b500 + v1000) == bounded_t{u32{1500}}); + BOOST_TEST((v5000 - b500) == bounded_t{u32{4500}}); + BOOST_TEST((v5000 / b1000) == bounded_t{u32{5}}); + BOOST_TEST((b1000 / v5000) == bounded_t{u32{0}}); + + // Comparisons + BOOST_TEST(v1000 == b1000); + BOOST_TEST(v5000 > b1000); + BOOST_TEST(b500 < v1000); +} + +// ============================================================================= +// Constexpr mixed operations +// ============================================================================= + +template +void test_constexpr_mixed_ops() +{ + constexpr auto v10 = VerifiedT{BasisT{10}}; + constexpr auto b5 = BasisT{5}; + + // constexpr arithmetic + constexpr auto sum = v10 + b5; + constexpr auto diff = v10 - b5; + constexpr auto prod = v10 * b5; + constexpr auto quot = v10 / b5; + constexpr auto mod = v10 % b5; + + static_assert(sum == BasisT{15}); + static_assert(diff == BasisT{5}); + static_assert(prod == BasisT{50}); + static_assert(quot == BasisT{2}); + static_assert(mod == BasisT{0}); + + // constexpr comparisons + static_assert(v10 == BasisT{10}); + static_assert(v10 != BasisT{5}); + static_assert(v10 > BasisT{5}); + static_assert(v10 < BasisT{20}); + static_assert((v10 <=> BasisT{10}) == std::strong_ordering::equal); +} + +// ============================================================================= +// Edge case: zero operations +// ============================================================================= + +template +void test_zero_operations() +{ + constexpr auto v0 = VerifiedT{BasisT{0}}; + constexpr auto v42 = VerifiedT{BasisT{42}}; + constexpr auto v1 = VerifiedT{BasisT{1}}; + + const auto b0 = BasisT{0}; + const auto b1 = BasisT{1}; + const auto b42 = BasisT{42}; + + // Adding zero + BOOST_TEST((v42 + b0) == BasisT{42}); + BOOST_TEST((b0 + v42) == BasisT{42}); + BOOST_TEST((v0 + b42) == BasisT{42}); + BOOST_TEST((b42 + v0) == BasisT{42}); + + // Subtracting zero + BOOST_TEST((v42 - b0) == BasisT{42}); + BOOST_TEST((b42 - v0) == BasisT{42}); + + // Multiplying by zero + BOOST_TEST((v42 * b0) == BasisT{0}); + BOOST_TEST((b0 * v42) == BasisT{0}); + + // Multiplying by one + BOOST_TEST((v42 * b1) == BasisT{42}); + BOOST_TEST((b42 * v1) == BasisT{42}); +} + +// ============================================================================= +// Edge case: max value comparisons +// ============================================================================= + +template +void test_max_value_comparisons() +{ + using underlying = detail::underlying_type_t; + + constexpr auto v_max = VerifiedT{BasisT{std::numeric_limits::max()}}; + constexpr auto v0 = VerifiedT{BasisT{0}}; + + const auto b_max = BasisT{std::numeric_limits::max()}; + const auto b0 = BasisT{0}; + + BOOST_TEST(v_max == b_max); + BOOST_TEST(v0 == b0); + BOOST_TEST(v_max > b0); + BOOST_TEST(v0 < b_max); + BOOST_TEST(b_max == v_max); + BOOST_TEST(b0 < v_max); +} + +// ============================================================================= +// Mixed bitwise ops: verified_uN with uN +// ============================================================================= + +template +void test_mixed_bitwise() +{ + constexpr auto v_ff = VerifiedT{BasisT{0xFF}}; + constexpr auto v_0f = VerifiedT{BasisT{0x0F}}; + constexpr auto v_1 = VerifiedT{BasisT{1}}; + constexpr auto v_8 = VerifiedT{BasisT{8}}; + + const auto b_f0 = BasisT{0xF0}; + const auto b_0f = BasisT{0x0F}; + const auto b_3 = BasisT{3}; + + // verified & basis + BOOST_TEST((v_ff & b_0f) == BasisT{0x0F}); + BOOST_TEST((v_0f & b_f0) == BasisT{0x00}); + + // basis & verified + BOOST_TEST((b_f0 & v_0f) == BasisT{0x00}); + BOOST_TEST((b_0f & v_ff) == BasisT{0x0F}); + + // verified | basis + BOOST_TEST((v_0f | b_f0) == BasisT{0xFF}); + + // basis | verified + BOOST_TEST((b_f0 | v_0f) == BasisT{0xFF}); + + // verified ^ basis + BOOST_TEST((v_ff ^ b_0f) == BasisT{0xF0}); + + // basis ^ verified + BOOST_TEST((b_0f ^ v_ff) == BasisT{0xF0}); + + // verified << basis + BOOST_TEST((v_1 << b_3) == BasisT{8}); + + // basis << verified + BOOST_TEST((BasisT{1} << v_1) == BasisT{2}); + + // verified >> basis + BOOST_TEST((v_8 >> b_3) == BasisT{1}); + + // basis >> verified + BOOST_TEST((BasisT{8} >> v_1) == BasisT{4}); +} + +// ============================================================================= +// Main +// ============================================================================= + +int main() +{ + // --- Mixed arithmetic: verified op basis --- + test_verified_op_basis_arithmetic(); + test_verified_op_basis_arithmetic(); + test_verified_op_basis_arithmetic(); + test_verified_op_basis_arithmetic(); + + // --- Mixed arithmetic: basis op verified --- + test_basis_op_verified_arithmetic(); + test_basis_op_verified_arithmetic(); + test_basis_op_verified_arithmetic(); + test_basis_op_verified_arithmetic(); + + // --- Asymmetric operand tests --- + test_asymmetric_operands(); + test_asymmetric_operands(); + test_asymmetric_operands(); + test_asymmetric_operands(); + + // --- Mixed comparisons --- + test_mixed_comparisons(); + test_mixed_comparisons(); + test_mixed_comparisons(); + test_mixed_comparisons(); + + // --- Constexpr mixed ops --- + test_constexpr_mixed_ops(); + test_constexpr_mixed_ops(); + test_constexpr_mixed_ops(); + test_constexpr_mixed_ops(); + + // --- Zero operations --- + test_zero_operations(); + test_zero_operations(); + test_zero_operations(); + test_zero_operations(); + + // --- Max value comparisons --- + test_max_value_comparisons(); + test_max_value_comparisons(); + test_max_value_comparisons(); + test_max_value_comparisons(); + + // --- Bounded integer mixed ops --- + test_bounded_mixed_arithmetic(); + test_bounded_mixed_comparisons(); + test_bounded_asymmetric_operands(); + test_bounded_u16_mixed_ops(); + test_bounded_u32_mixed_ops(); + + // --- Mixed bitwise --- + test_mixed_bitwise(); + test_mixed_bitwise(); + test_mixed_bitwise(); + test_mixed_bitwise(); + + return boost::report_errors(); +}