From 2dc4c8caf8b5898fbaf4d198c7f1a23674cd5545 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 4 Feb 2026 15:15:17 -0500 Subject: [PATCH 1/4] Add symlink to examples folder --- doc/modules/ROOT/examples | 1 + 1 file changed, 1 insertion(+) create mode 120000 doc/modules/ROOT/examples diff --git a/doc/modules/ROOT/examples b/doc/modules/ROOT/examples new file mode 120000 index 0000000..9f9d1de --- /dev/null +++ b/doc/modules/ROOT/examples @@ -0,0 +1 @@ +../../../examples \ No newline at end of file From 9c7ddc5a295887c7bb0c1149060f9e5e9dede299 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 4 Feb 2026 15:15:32 -0500 Subject: [PATCH 2/4] Add examples of different arithmetic types --- examples/checked_arithmetic.cpp | 111 ++++++++++++++++++++++++++++ examples/overflowing_arithmetic.cpp | 95 ++++++++++++++++++++++++ examples/saturating_arithmetic.cpp | 66 +++++++++++++++++ examples/wrapping_arithmetic.cpp | 96 ++++++++++++++++++++++++ test/Jamfile | 4 + 5 files changed, 372 insertions(+) create mode 100644 examples/checked_arithmetic.cpp create mode 100644 examples/overflowing_arithmetic.cpp create mode 100644 examples/saturating_arithmetic.cpp create mode 100644 examples/wrapping_arithmetic.cpp diff --git a/examples/checked_arithmetic.cpp b/examples/checked_arithmetic.cpp new file mode 100644 index 0000000..cd59331 --- /dev/null +++ b/examples/checked_arithmetic.cpp @@ -0,0 +1,111 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +//[checked_arithmetic_example +//` This example demonstrates the use of checked arithmetic operations. +//` These functions return std::optional - containing the result if the +//` operation succeeded, or std::nullopt if overflow/underflow occurred. +//` This provides a clean, exception-free way to handle arithmetic errors. + +#include +#include +#include +#include +#include +#include + +int main() +{ + using boost::safe_numbers::u32; + using boost::safe_numbers::checked_add; + using boost::safe_numbers::checked_sub; + using boost::safe_numbers::checked_mul; + using boost::safe_numbers::checked_div; + + // Checked addition: returns nullopt on overflow + { + const u32 a {std::numeric_limits::max()}; + const u32 b {100U}; + const auto result {checked_add(a, b)}; + + if (result.has_value()) + { + std::cout << "checked_add(max, 100) = " << *result << std::endl; + } + else + { + std::cout << "checked_add(max, 100) = overflow detected!" << std::endl; + } + // Output: overflow detected! + } + + // Checked subtraction: returns nullopt on underflow + { + const u32 a {10U}; + const u32 b {100U}; + const auto result {checked_sub(a, b)}; + + if (result.has_value()) + { + std::cout << "checked_sub(10, 100) = " << *result << std::endl; + } + else + { + std::cout << "checked_sub(10, 100) = underflow detected!" << std::endl; + } + // Output: underflow detected! + } + + // Checked division: returns nullopt on divide by zero + { + const u32 a {100U}; + const u32 b {0U}; + const auto result {checked_div(a, b)}; + + if (result.has_value()) + { + std::cout << "checked_div(100, 0) = " << *result << std::endl; + } + else + { + std::cout << "checked_div(100, 0) = division by zero!" << std::endl; + } + // Output: division by zero! + } + + // Successful operations return the value wrapped in optional + { + const u32 a {100U}; + const u32 b {50U}; + + // Using value_or for safe access with default + std::cout << "checked_add(100, 50) = " + << checked_add(a, b).value_or(u32{0U}) << std::endl; + std::cout << "checked_sub(100, 50) = " + << checked_sub(a, b).value_or(u32{0U}) << std::endl; + std::cout << "checked_mul(100, 50) = " + << checked_mul(a, b).value_or(u32{0U}) << std::endl; + // Output: 150, 50, 5000 + } + + // Chaining checked operations with value_or + { + const u32 a {1000000000U}; + const u32 b {5U}; + + // Only proceed if multiplication doesn't overflow + const auto product {checked_mul(a, b)}; + if (product) + { + std::cout << "Safe: " << a << " * " << b << " = " << *product << std::endl; + } + else + { + std::cout << "Operation would overflow, using fallback" << std::endl; + } + } + + return 0; +} +//] diff --git a/examples/overflowing_arithmetic.cpp b/examples/overflowing_arithmetic.cpp new file mode 100644 index 0000000..dd53d8b --- /dev/null +++ b/examples/overflowing_arithmetic.cpp @@ -0,0 +1,95 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +//[overflowing_arithmetic_example +//` This example demonstrates the use of overflowing arithmetic operations. +//` These functions return a std::pair containing the result and a boolean +//` flag indicating whether overflow/underflow occurred. The result is the +//` wrapped value (as if using normal unsigned arithmetic). + +#include +#include +#include +#include +#include +#include + +int main() +{ + using boost::safe_numbers::u32; + using boost::safe_numbers::overflowing_add; + using boost::safe_numbers::overflowing_sub; + using boost::safe_numbers::overflowing_mul; + + // Overflowing addition: returns (wrapped_result, did_overflow) + { + const u32 a {std::numeric_limits::max()}; + const u32 b {100U}; + const auto [result, overflowed] {overflowing_add(a, b)}; + + std::cout << "overflowing_add(max, 100):" << std::endl; + std::cout << " result = " << result << std::endl; + std::cout << " overflowed = " << std::boolalpha << overflowed << std::endl; + // Output: result = 99 (wrapped), overflowed = true + } + + // Overflowing subtraction: returns (wrapped_result, did_underflow) + { + const u32 a {10U}; + const u32 b {100U}; + const auto [result, underflowed] {overflowing_sub(a, b)}; + + std::cout << "overflowing_sub(10, 100):" << std::endl; + std::cout << " result = " << result << std::endl; + std::cout << " underflowed = " << std::boolalpha << underflowed << std::endl; + // Output: result = 4294967206 (wrapped), underflowed = true + } + + // Overflowing multiplication: returns (wrapped_result, did_overflow) + { + const u32 a {std::numeric_limits::max()}; + const u32 b {2U}; + const auto [result, overflowed] {overflowing_mul(a, b)}; + + std::cout << "overflowing_mul(max, 2):" << std::endl; + std::cout << " result = " << result << std::endl; + std::cout << " overflowed = " << std::boolalpha << overflowed << std::endl; + // Output: result = 4294967294 (wrapped), overflowed = true + } + + // Normal operations that don't overflow + { + const u32 a {100U}; + const u32 b {50U}; + const auto [add_result, add_overflow] {overflowing_add(a, b)}; + const auto [sub_result, sub_overflow] {overflowing_sub(a, b)}; + const auto [mul_result, mul_overflow] {overflowing_mul(a, b)}; + + std::cout << "overflowing_add(100, 50) = " << add_result + << " (overflow: " << add_overflow << ")" << std::endl; + std::cout << "overflowing_sub(100, 50) = " << sub_result + << " (overflow: " << sub_overflow << ")" << std::endl; + std::cout << "overflowing_mul(100, 50) = " << mul_result + << " (overflow: " << mul_overflow << ")" << std::endl; + // Output: 150/false, 50/false, 5000/false + } + + // Using the overflow flag for conditional logic + { + const u32 a {1000000000U}; + const u32 b {5U}; + + if (const auto [result, overflowed] {overflowing_mul(a, b)}; !overflowed) + { + std::cout << "Safe multiplication: " << a << " * " << b << " = " << result << std::endl; + } + else + { + std::cout << "Multiplication would overflow!" << std::endl; + } + } + + return 0; +} +//] diff --git a/examples/saturating_arithmetic.cpp b/examples/saturating_arithmetic.cpp new file mode 100644 index 0000000..a27de8f --- /dev/null +++ b/examples/saturating_arithmetic.cpp @@ -0,0 +1,66 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +//[saturating_arithmetic_example +//` This example demonstrates the use of saturating arithmetic operations. +//` When overflow or underflow would occur, the result saturates at the +//` type's maximum or minimum value instead of wrapping or throwing. + +#include +#include +#include +#include +#include + +int main() +{ + using boost::safe_numbers::u32; + using boost::safe_numbers::saturating_add; + using boost::safe_numbers::saturating_sub; + using boost::safe_numbers::saturating_mul; + + // Saturating addition: clamps to max on overflow + { + const u32 a {std::numeric_limits::max()}; + const u32 b {100U}; + const u32 result {saturating_add(a, b)}; + + std::cout << "saturating_add(max, 100) = " << result << std::endl; + // Output: 4294967295 (UINT32_MAX, saturated) + } + + // Saturating subtraction: clamps to min (0) on underflow + { + const u32 a {10U}; + const u32 b {100U}; + const u32 result {saturating_sub(a, b)}; + + std::cout << "saturating_sub(10, 100) = " << result << std::endl; + // Output: 0 (saturated at minimum) + } + + // Saturating multiplication: clamps to max on overflow + { + const u32 a {std::numeric_limits::max()}; + const u32 b {2U}; + const u32 result {saturating_mul(a, b)}; + + std::cout << "saturating_mul(max, 2) = " << result << std::endl; + // Output: 4294967295 (UINT32_MAX, saturated) + } + + // Normal operations that don't overflow work as expected + { + const u32 a {100U}; + const u32 b {50U}; + + std::cout << "saturating_add(100, 50) = " << saturating_add(a, b) << std::endl; + std::cout << "saturating_sub(100, 50) = " << saturating_sub(a, b) << std::endl; + std::cout << "saturating_mul(100, 50) = " << saturating_mul(a, b) << std::endl; + // Output: 150, 50, 5000 + } + + return 0; +} +//] diff --git a/examples/wrapping_arithmetic.cpp b/examples/wrapping_arithmetic.cpp new file mode 100644 index 0000000..81036de --- /dev/null +++ b/examples/wrapping_arithmetic.cpp @@ -0,0 +1,96 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +//[wrapping_arithmetic_example +//` This example demonstrates the use of wrapping arithmetic operations. +//` These functions perform standard C unsigned integer wrapping behavior - +//` when overflow or underflow occurs, the result wraps around modulo 2^N. +//` This matches the behavior of built-in unsigned integers in C/C++. + +#include +#include +#include +#include +#include + +int main() +{ + using boost::safe_numbers::u8; + using boost::safe_numbers::u32; + using boost::safe_numbers::wrapping_add; + using boost::safe_numbers::wrapping_sub; + using boost::safe_numbers::wrapping_mul; + + // Wrapping addition: wraps around on overflow + { + const u8 a {255U}; // UINT8_MAX + const u8 b {2U}; + const u8 result {wrapping_add(a, b)}; + + std::cout << "wrapping_add(255, 2) = " << result << std::endl; + // Output: 1 (255 + 2 = 257, wraps to 257 % 256 = 1) + } + + // Wrapping subtraction: wraps around on underflow + { + const u8 a {0U}; + const u8 b {1U}; + const u8 result {wrapping_sub(a, b)}; + + std::cout << "wrapping_sub(0, 1) = " << result << std::endl; + // Output: 255 (wraps to UINT8_MAX) + } + + // Wrapping multiplication: wraps around on overflow + { + const u8 a {200U}; + const u8 b {2U}; + const u8 result {wrapping_mul(a, b)}; + + std::cout << "wrapping_mul(200, 2) = " << result << std::endl; + // Output: 144 (200 * 2 = 400, wraps to 400 % 256 = 144) + } + + // Demonstration with u32 + { + const u32 max_val {std::numeric_limits::max()}; + const u32 one {1U}; + + std::cout << "wrapping_add(UINT32_MAX, 1) = " + << wrapping_add(max_val, one) << std::endl; + // Output: 0 (wraps around) + + const u32 zero {0U}; + std::cout << "wrapping_sub(0, 1) = " + << wrapping_sub(zero, one) << std::endl; + // Output: 4294967295 (UINT32_MAX) + } + + // Normal operations that don't overflow work as expected + { + const u32 a {100U}; + const u32 b {50U}; + + std::cout << "wrapping_add(100, 50) = " << wrapping_add(a, b) << std::endl; + std::cout << "wrapping_sub(100, 50) = " << wrapping_sub(a, b) << std::endl; + std::cout << "wrapping_mul(100, 50) = " << wrapping_mul(a, b) << std::endl; + // Output: 150, 50, 5000 + } + + // Useful for implementing counters that wrap + { + u8 counter {254U}; + std::cout << "Counter sequence: "; + for (int i = 0; i < 5; ++i) + { + std::cout << counter << " "; + counter = wrapping_add(counter, u8{1U}); + } + std::cout << std::endl; + // Output: 254 255 0 1 2 + } + + return 0; +} +//] diff --git a/test/Jamfile b/test/Jamfile index bc0e9f6..60ec1da 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -105,3 +105,7 @@ run ../examples/basic_usage.cpp ; compile-fail ../examples/compile_fail_basic_usage_constexpr.cpp ; run ../examples/debugger.cpp ; run ../examples/construction_and_conversion.cpp ; +run ../examples/saturating_arithmetic.cpp ; +run ../examples/overflowing_arithmetic.cpp ; +run ../examples/checked_arithmetic.cpp ; +run ../examples/wrapping_arithmetic.cpp ; From 75cfc93d1da37ddec1292af3f0d107e61db5798b Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 4 Feb 2026 15:19:55 -0500 Subject: [PATCH 3/4] Add examples of each overflow policy --- doc/modules/ROOT/nav.adoc | 7 + doc/modules/ROOT/pages/examples.adoc | 185 +++++++++++++++++++++++++++ 2 files changed, 192 insertions(+) create mode 100644 doc/modules/ROOT/pages/examples.adoc diff --git a/doc/modules/ROOT/nav.adoc b/doc/modules/ROOT/nav.adoc index e97a454..b28fd5d 100644 --- a/doc/modules/ROOT/nav.adoc +++ b/doc/modules/ROOT/nav.adoc @@ -5,3 +5,10 @@ ** xref:api_reference.adoc#api_headers[Headers] * xref:unsigned_integers.adoc[] * xref:charconv.adoc[] +* xref:examples.adoc[] +** xref:examples.adoc#examples_basic_usage[Basic Usage] +** xref:examples.adoc#examples_construction[Construction and Conversion] +** xref:examples.adoc#examples_saturating[Saturating Arithmetic] +** xref:examples.adoc#examples_overflowing[Overflowing Arithmetic] +** xref:examples.adoc#examples_checked[Checked Arithmetic] +** xref:examples.adoc#examples_wrapping[Wrapping Arithmetic] diff --git a/doc/modules/ROOT/pages/examples.adoc b/doc/modules/ROOT/pages/examples.adoc new file mode 100644 index 0000000..d5836a9 --- /dev/null +++ b/doc/modules/ROOT/pages/examples.adoc @@ -0,0 +1,185 @@ +//// +Copyright 2025 Matt Borland +Distributed under the Boost Software License, Version 1.0. +https://www.boost.org/LICENSE_1_0.txt +//// + +[#examples] += Examples +:idprefix: examples_ + +All the following examples can be found in the examples/ folder of the library. + +[#examples_basic_usage] +== Basic Usage (Default Throwing Behavior) + +.This https://github.com/boostorg/safe_numbers/blob/develop/examples/basic_usage.cpp[example] demonstrates the default behavior where arithmetic overflow throws an exception +==== +[source, c++] +---- +include::example$basic_usage.cpp[] +---- + +.Expected Output +[listing] +---- +Error Detected: Overflow detected in unsigned addition +---- +==== + +[#examples_construction] +== Construction and Conversion + +.This https://github.com/boostorg/safe_numbers/blob/develop/examples/construction_and_conversion.cpp[example] demonstrates safe integer construction and conversion +==== +[source, c++] +---- +include::example$construction_and_conversion.cpp[] +---- +==== + +[#examples_saturating] +== Saturating Arithmetic + +Saturating arithmetic clamps results to the type's minimum or maximum value instead of overflowing or throwing. +This is useful when you want bounded behavior without exceptions. + +.This https://github.com/boostorg/safe_numbers/blob/develop/examples/saturating_arithmetic.cpp[example] demonstrates saturating arithmetic operations +==== +[source, c++] +---- +include::example$saturating_arithmetic.cpp[] +---- + +.Expected Output +[listing] +---- +saturating_add(max, 100) = 4294967295 +saturating_sub(10, 100) = 0 +saturating_mul(max, 2) = 4294967295 +saturating_add(100, 50) = 150 +saturating_sub(100, 50) = 50 +saturating_mul(100, 50) = 5000 +---- +==== + +[#examples_overflowing] +== Overflowing Arithmetic + +Overflowing arithmetic returns both the wrapped result and a boolean flag indicating whether overflow occurred. +This gives you access to both the C-style wrapped value and overflow detection in a single operation. + +.This https://github.com/boostorg/safe_numbers/blob/develop/examples/overflowing_arithmetic.cpp[example] demonstrates overflowing arithmetic operations +==== +[source, c++] +---- +include::example$overflowing_arithmetic.cpp[] +---- + +.Expected Output +[listing] +---- +overflowing_add(max, 100): + result = 99 + overflowed = true +overflowing_sub(10, 100): + result = 4294967206 + underflowed = true +overflowing_mul(max, 2): + result = 4294967294 + overflowed = true +overflowing_add(100, 50) = 150 (overflow: false) +overflowing_sub(100, 50) = 50 (overflow: false) +overflowing_mul(100, 50) = 5000 (overflow: false) +Safe multiplication: 1000000000 * 5 = 5000000000 +---- +==== + +[#examples_checked] +== Checked Arithmetic + +Checked arithmetic returns `std::optional` - containing the result on success, or `std::nullopt` on overflow. +This provides exception-free error handling with a clean, idiomatic interface. + +.This https://github.com/boostorg/safe_numbers/blob/develop/examples/checked_arithmetic.cpp[example] demonstrates checked arithmetic operations +==== +[source, c++] +---- +include::example$checked_arithmetic.cpp[] +---- + +.Expected Output +[listing] +---- +checked_add(max, 100) = overflow detected! +checked_sub(10, 100) = underflow detected! +checked_div(100, 0) = division by zero! +checked_add(100, 50) = 150 +checked_sub(100, 50) = 50 +checked_mul(100, 50) = 5000 +Safe: 1000000000 * 5 = 5000000000 +---- +==== + +[#examples_wrapping] +== Wrapping Arithmetic + +Wrapping arithmetic performs standard C unsigned integer wrapping behavior - results wrap around modulo 2^N. +This matches the behavior of built-in unsigned integers and is useful for implementing counters, checksums, or hash functions. + +.This https://github.com/boostorg/safe_numbers/blob/develop/examples/wrapping_arithmetic.cpp[example] demonstrates wrapping arithmetic operations +==== +[source, c++] +---- +include::example$wrapping_arithmetic.cpp[] +---- + +.Expected Output +[listing] +---- +wrapping_add(255, 2) = 1 +wrapping_sub(0, 1) = 255 +wrapping_mul(200, 2) = 144 +wrapping_add(UINT32_MAX, 1) = 0 +wrapping_sub(0, 1) = 4294967295 +wrapping_add(100, 50) = 150 +wrapping_sub(100, 50) = 50 +wrapping_mul(100, 50) = 5000 +Counter sequence: 254 255 0 1 2 +---- +==== + +[#examples_policy_comparison] +== Policy Comparison + +The following table summarizes the behavior of each arithmetic policy: + +[cols="1,2,2,2"] +|=== +|Policy |On Overflow |Return Type |Use Case + +|Default (operators) +|Throws `std::overflow_error` +|`T` +|When overflow is a programming error + +|Saturating +|Clamps to min/max +|`T` +|Bounded values, DSP, graphics + +|Overflowing +|Returns wrapped value + flag +|`std::pair` +|Need both wrapped value and detection + +|Checked +|Returns `std::nullopt` +|`std::optional` +|Exception-free error handling + +|Wrapping +|Wraps around (modulo 2^N) +|`T` +|Counters, checksums, hashing +|=== From 609aef53972286d540d0d40c8d90867e4a9c5ccf Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 4 Feb 2026 16:04:44 -0500 Subject: [PATCH 4/4] Exhaustively test each policy --- test/Jamfile | 4 + test/test_boundary_arithmetic.cpp | 659 +++++++++++++++++++++++++ test/test_exhaustive_u8_arithmetic.cpp | 371 ++++++++++++++ 3 files changed, 1034 insertions(+) create mode 100644 test/test_boundary_arithmetic.cpp create mode 100644 test/test_exhaustive_u8_arithmetic.cpp diff --git a/test/Jamfile b/test/Jamfile index 60ec1da..e9c0e88 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -91,6 +91,10 @@ run test_unsigned_wrapping_multiplication.cpp ; run test_unsigned_wrapping_division.cpp ; run test_unsigned_wrapping_mod.cpp ; +# Exhaustive verification tests +run test_exhaustive_u8_arithmetic.cpp ; +run test_boundary_arithmetic.cpp ; + # Compile Tests compile compile_tests/compile_test_unsigned_integers.cpp ; compile compile_tests/compile_test_iostream.cpp ; diff --git a/test/test_boundary_arithmetic.cpp b/test/test_boundary_arithmetic.cpp new file mode 100644 index 0000000..d896ff1 --- /dev/null +++ b/test/test_boundary_arithmetic.cpp @@ -0,0 +1,659 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// +// Boundary tests for u16, u32, u64, u128 arithmetic operations. +// Tests mathematically significant boundary values for all operations and policies. + +#include + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include +#include +#include + +#endif + +using namespace boost::safe_numbers; +using boost::int128::uint128_t; + +// ============================================ +// Boundary value generators +// ============================================ + +template +auto get_boundary_values() -> std::vector +{ + constexpr auto max_val {std::numeric_limits::max()}; + constexpr auto mid_val {max_val / 2U}; + + std::vector values; + + // Near minimum + values.push_back(0U); + values.push_back(1U); + values.push_back(2U); + + // Near maximum + values.push_back(static_cast(max_val - 2U)); + values.push_back(static_cast(max_val - 1U)); + values.push_back(max_val); + + // Midpoint + values.push_back(static_cast(mid_val - 1U)); + values.push_back(mid_val); + values.push_back(static_cast(mid_val + 1U)); + + // Powers of 2 (up to bit width) + for (unsigned k {2}; k < sizeof(BasisType) * 8U; ++k) + { + const auto pow2 {static_cast(BasisType{1U} << k)}; + if (pow2 > 2U && pow2 < max_val - 1U) + { + values.push_back(static_cast(pow2 - 1U)); + values.push_back(pow2); + values.push_back(static_cast(pow2 + 1U)); + } + } + + return values; +} + +// ============================================ +// Helper type to get wider type for verification +// ============================================ + +template +struct wider_type; + +template <> +struct wider_type +{ + using type = std::uint32_t; +}; + +template <> +struct wider_type +{ + using type = std::uint64_t; +}; + +template <> +struct wider_type +{ + using type = uint128_t; +}; + +// For u128, we use the same type but handle overflow detection differently +template <> +struct wider_type +{ + using type = uint128_t; +}; + +template +using wider_type_t = typename wider_type::type; + +// ============================================ +// Addition boundary tests +// ============================================ + +template +void test_boundary_saturating_add() +{ + const auto boundary_vals {get_boundary_values()}; + constexpr auto max_val {std::numeric_limits::max()}; + + for (const auto lhs_val : boundary_vals) + { + for (const auto rhs_val : boundary_vals) + { + const SafeT lhs {lhs_val}; + const SafeT rhs {rhs_val}; + + // Use wider type to compute expected value + using wider = wider_type_t; + const auto wide_sum {static_cast(lhs_val) + static_cast(rhs_val)}; + const auto expected {static_cast(wide_sum > static_cast(max_val) ? max_val : static_cast(wide_sum))}; + + const auto result {saturating_add(lhs, rhs)}; + BOOST_TEST_EQ(static_cast(result), expected); + } + } +} + +template +void test_boundary_overflowing_add() +{ + const auto boundary_vals {get_boundary_values()}; + constexpr auto max_val {std::numeric_limits::max()}; + + for (const auto lhs_val : boundary_vals) + { + for (const auto rhs_val : boundary_vals) + { + const SafeT lhs {lhs_val}; + const SafeT rhs {rhs_val}; + + using wider = wider_type_t; + const auto wide_sum {static_cast(lhs_val) + static_cast(rhs_val)}; + const auto expected_result {static_cast(wide_sum)}; + const auto expected_overflow {wide_sum > static_cast(max_val)}; + + const auto [result, overflowed] {overflowing_add(lhs, rhs)}; + BOOST_TEST_EQ(static_cast(result), expected_result); + BOOST_TEST_EQ(overflowed, expected_overflow); + } + } +} + +template +void test_boundary_checked_add() +{ + const auto boundary_vals {get_boundary_values()}; + constexpr auto max_val {std::numeric_limits::max()}; + + for (const auto lhs_val : boundary_vals) + { + for (const auto rhs_val : boundary_vals) + { + const SafeT lhs {lhs_val}; + const SafeT rhs {rhs_val}; + + // For u128, the wider type is the same as the type itself, + // so we need to detect overflow by checking if sum < lhs + using wider = wider_type_t; + const auto wide_sum {static_cast(lhs_val) + static_cast(rhs_val)}; + + bool should_overflow {}; + if constexpr (std::is_same_v) + { + // For u128, overflow if wrap-around occurred + should_overflow = wide_sum < lhs_val; + } + else + { + should_overflow = wide_sum > static_cast(max_val); + } + + const auto result {checked_add(lhs, rhs)}; + + if (should_overflow) + { + BOOST_TEST(!result.has_value()); + } + else + { + BOOST_TEST(result.has_value()); + BOOST_TEST_EQ(static_cast(result.value()), static_cast(wide_sum)); + } + } + } +} + +template +void test_boundary_wrapping_add() +{ + const auto boundary_vals {get_boundary_values()}; + + for (const auto lhs_val : boundary_vals) + { + for (const auto rhs_val : boundary_vals) + { + const SafeT lhs {lhs_val}; + const SafeT rhs {rhs_val}; + + using wider = wider_type_t; + const auto wide_sum {static_cast(lhs_val) + static_cast(rhs_val)}; + const auto expected {static_cast(wide_sum)}; + + const auto result {wrapping_add(lhs, rhs)}; + BOOST_TEST_EQ(static_cast(result), expected); + } + } +} + +// ============================================ +// Subtraction boundary tests +// ============================================ + +template +void test_boundary_saturating_sub() +{ + const auto boundary_vals {get_boundary_values()}; + + for (const auto lhs_val : boundary_vals) + { + for (const auto rhs_val : boundary_vals) + { + const SafeT lhs {lhs_val}; + const SafeT rhs {rhs_val}; + + const auto expected {static_cast(rhs_val > lhs_val ? BasisType{0U} : lhs_val - rhs_val)}; + + const auto result {saturating_sub(lhs, rhs)}; + BOOST_TEST_EQ(static_cast(result), expected); + } + } +} + +template +void test_boundary_overflowing_sub() +{ + const auto boundary_vals {get_boundary_values()}; + + for (const auto lhs_val : boundary_vals) + { + for (const auto rhs_val : boundary_vals) + { + const SafeT lhs {lhs_val}; + const SafeT rhs {rhs_val}; + + // Wrapping subtraction + const auto expected_result {static_cast(lhs_val - rhs_val)}; + const auto expected_underflow {rhs_val > lhs_val}; + + const auto [result, underflowed] {overflowing_sub(lhs, rhs)}; + BOOST_TEST_EQ(static_cast(result), expected_result); + BOOST_TEST_EQ(underflowed, expected_underflow); + } + } +} + +template +void test_boundary_checked_sub() +{ + const auto boundary_vals {get_boundary_values()}; + + for (const auto lhs_val : boundary_vals) + { + for (const auto rhs_val : boundary_vals) + { + const SafeT lhs {lhs_val}; + const SafeT rhs {rhs_val}; + + const auto should_underflow {rhs_val > lhs_val}; + + const auto result {checked_sub(lhs, rhs)}; + + if (should_underflow) + { + BOOST_TEST(!result.has_value()); + } + else + { + BOOST_TEST(result.has_value()); + BOOST_TEST_EQ(static_cast(result.value()), static_cast(lhs_val - rhs_val)); + } + } + } +} + +template +void test_boundary_wrapping_sub() +{ + const auto boundary_vals {get_boundary_values()}; + + for (const auto lhs_val : boundary_vals) + { + for (const auto rhs_val : boundary_vals) + { + const SafeT lhs {lhs_val}; + const SafeT rhs {rhs_val}; + + const auto expected {static_cast(lhs_val - rhs_val)}; + + const auto result {wrapping_sub(lhs, rhs)}; + BOOST_TEST_EQ(static_cast(result), expected); + } + } +} + +// ============================================ +// Multiplication boundary tests +// ============================================ + +template +void test_boundary_saturating_mul() +{ + const auto boundary_vals {get_boundary_values()}; + constexpr auto max_val {std::numeric_limits::max()}; + + for (const auto lhs_val : boundary_vals) + { + for (const auto rhs_val : boundary_vals) + { + const SafeT lhs {lhs_val}; + const SafeT rhs {rhs_val}; + + using wider = wider_type_t; + const auto wide_product {static_cast(lhs_val) * static_cast(rhs_val)}; + const auto expected {static_cast(wide_product > static_cast(max_val) ? max_val : static_cast(wide_product))}; + + const auto result {saturating_mul(lhs, rhs)}; + BOOST_TEST_EQ(static_cast(result), expected); + } + } +} + +template +void test_boundary_overflowing_mul() +{ + const auto boundary_vals {get_boundary_values()}; + constexpr auto max_val {std::numeric_limits::max()}; + + for (const auto lhs_val : boundary_vals) + { + for (const auto rhs_val : boundary_vals) + { + const SafeT lhs {lhs_val}; + const SafeT rhs {rhs_val}; + + using wider = wider_type_t; + const auto wide_product {static_cast(lhs_val) * static_cast(rhs_val)}; + const auto expected_result {static_cast(wide_product)}; + const auto expected_overflow {wide_product > static_cast(max_val)}; + + const auto [result, overflowed] {overflowing_mul(lhs, rhs)}; + BOOST_TEST_EQ(static_cast(result), expected_result); + BOOST_TEST_EQ(overflowed, expected_overflow); + } + } +} + +template +void test_boundary_checked_mul() +{ + const auto boundary_vals {get_boundary_values()}; + constexpr auto max_val {std::numeric_limits::max()}; + + for (const auto lhs_val : boundary_vals) + { + for (const auto rhs_val : boundary_vals) + { + const SafeT lhs {lhs_val}; + const SafeT rhs {rhs_val}; + + // For u128, the wider type is the same as the type itself + using wider = wider_type_t; + const auto wide_product {static_cast(lhs_val) * static_cast(rhs_val)}; + + bool should_overflow {}; + if constexpr (std::is_same_v) + { + // For u128, overflow if rhs != 0 and lhs > max/rhs + should_overflow = rhs_val != BasisType{0U} && lhs_val > max_val / rhs_val; + } + else + { + should_overflow = wide_product > static_cast(max_val); + } + + const auto result {checked_mul(lhs, rhs)}; + + if (should_overflow) + { + BOOST_TEST(!result.has_value()); + } + else + { + BOOST_TEST(result.has_value()); + BOOST_TEST_EQ(static_cast(result.value()), static_cast(wide_product)); + } + } + } +} + +template +void test_boundary_wrapping_mul() +{ + const auto boundary_vals {get_boundary_values()}; + + for (const auto lhs_val : boundary_vals) + { + for (const auto rhs_val : boundary_vals) + { + const SafeT lhs {lhs_val}; + const SafeT rhs {rhs_val}; + + using wider = wider_type_t; + const auto wide_product {static_cast(lhs_val) * static_cast(rhs_val)}; + const auto expected {static_cast(wide_product)}; + + const auto result {wrapping_mul(lhs, rhs)}; + BOOST_TEST_EQ(static_cast(result), expected); + } + } +} + +// ============================================ +// Division boundary tests +// ============================================ + +template +void test_boundary_checked_div() +{ + const auto boundary_vals {get_boundary_values()}; + + for (const auto lhs_val : boundary_vals) + { + for (const auto rhs_val : boundary_vals) + { + const SafeT lhs {lhs_val}; + const SafeT rhs {rhs_val}; + + const auto result {checked_div(lhs, rhs)}; + + if (rhs_val == 0U) + { + BOOST_TEST(!result.has_value()); + } + else + { + BOOST_TEST(result.has_value()); + BOOST_TEST_EQ(static_cast(result.value()), static_cast(lhs_val / rhs_val)); + } + } + } +} + +// ============================================ +// Modulo boundary tests +// ============================================ + +template +void test_boundary_checked_mod() +{ + const auto boundary_vals {get_boundary_values()}; + + for (const auto lhs_val : boundary_vals) + { + for (const auto rhs_val : boundary_vals) + { + const SafeT lhs {lhs_val}; + const SafeT rhs {rhs_val}; + + const auto result {checked_mod(lhs, rhs)}; + + if (rhs_val == 0U) + { + BOOST_TEST(!result.has_value()); + } + else + { + BOOST_TEST(result.has_value()); + BOOST_TEST_EQ(static_cast(result.value()), static_cast(lhs_val % rhs_val)); + } + } + } +} + +// ============================================ +// u128 specific tests (no wider type available) +// Uses overflow detection from library +// ============================================ + +void test_boundary_u128_saturating_add() +{ + const auto boundary_vals {get_boundary_values()}; + constexpr auto max_val {std::numeric_limits::max()}; + + for (const auto lhs_val : boundary_vals) + { + for (const auto rhs_val : boundary_vals) + { + const u128 lhs {lhs_val}; + const u128 rhs {rhs_val}; + + // Detect overflow: sum would wrap if sum < lhs + const auto sum {static_cast(lhs_val + rhs_val)}; + const auto would_overflow {sum < lhs_val}; + const auto expected {would_overflow ? max_val : sum}; + + const auto result {saturating_add(lhs, rhs)}; + BOOST_TEST_EQ(static_cast(result), expected); + } + } +} + +void test_boundary_u128_overflowing_add() +{ + const auto boundary_vals {get_boundary_values()}; + + for (const auto lhs_val : boundary_vals) + { + for (const auto rhs_val : boundary_vals) + { + const u128 lhs {lhs_val}; + const u128 rhs {rhs_val}; + + const auto sum {static_cast(lhs_val + rhs_val)}; + const auto expected_overflow {sum < lhs_val}; + + const auto [result, overflowed] {overflowing_add(lhs, rhs)}; + BOOST_TEST_EQ(static_cast(result), sum); + BOOST_TEST_EQ(overflowed, expected_overflow); + } + } +} + +void test_boundary_u128_saturating_mul() +{ + const auto boundary_vals {get_boundary_values()}; + constexpr auto max_val {std::numeric_limits::max()}; + + for (const auto lhs_val : boundary_vals) + { + for (const auto rhs_val : boundary_vals) + { + const u128 lhs {lhs_val}; + const u128 rhs {rhs_val}; + + // Detect overflow: if rhs != 0 and lhs > max/rhs, overflow + const auto would_overflow {rhs_val != 0U && lhs_val > max_val / rhs_val}; + const auto product {static_cast(lhs_val * rhs_val)}; + const auto expected {would_overflow ? max_val : product}; + + const auto result {saturating_mul(lhs, rhs)}; + BOOST_TEST_EQ(static_cast(result), expected); + } + } +} + +void test_boundary_u128_overflowing_mul() +{ + const auto boundary_vals {get_boundary_values()}; + constexpr auto max_val {std::numeric_limits::max()}; + + for (const auto lhs_val : boundary_vals) + { + for (const auto rhs_val : boundary_vals) + { + const u128 lhs {lhs_val}; + const u128 rhs {rhs_val}; + + const auto expected_overflow {rhs_val != 0U && lhs_val > max_val / rhs_val}; + const auto product {static_cast(lhs_val * rhs_val)}; + + const auto [result, overflowed] {overflowing_mul(lhs, rhs)}; + BOOST_TEST_EQ(static_cast(result), product); + BOOST_TEST_EQ(overflowed, expected_overflow); + } + } +} + +// ============================================ +// Test runners for each type +// ============================================ + +template +void run_all_boundary_tests() +{ + // Addition + test_boundary_saturating_add(); + test_boundary_overflowing_add(); + test_boundary_checked_add(); + test_boundary_wrapping_add(); + + // Subtraction + test_boundary_saturating_sub(); + test_boundary_overflowing_sub(); + test_boundary_checked_sub(); + test_boundary_wrapping_sub(); + + // Multiplication + test_boundary_saturating_mul(); + test_boundary_overflowing_mul(); + test_boundary_checked_mul(); + test_boundary_wrapping_mul(); + + // Division (checked only, others throw on div by zero) + test_boundary_checked_div(); + + // Modulo (checked only, others throw on mod by zero) + test_boundary_checked_mod(); +} + +void run_u128_boundary_tests() +{ + // u128 needs special handling since there's no wider type + test_boundary_u128_saturating_add(); + test_boundary_u128_overflowing_add(); + test_boundary_saturating_sub(); + test_boundary_overflowing_sub(); + test_boundary_checked_sub(); + test_boundary_wrapping_sub(); + test_boundary_u128_saturating_mul(); + test_boundary_u128_overflowing_mul(); + test_boundary_checked_div(); + test_boundary_checked_mod(); + + // These use the generic templates which work for subtraction, div, mod + test_boundary_checked_add(); + test_boundary_wrapping_add(); + test_boundary_checked_mul(); + test_boundary_wrapping_mul(); +} + +int main() +{ + // u16 boundary tests + run_all_boundary_tests(); + + // u32 boundary tests + run_all_boundary_tests(); + + // u64 boundary tests + run_all_boundary_tests(); + + // u128 boundary tests (special handling) + run_u128_boundary_tests(); + + return boost::report_errors(); +} diff --git a/test/test_exhaustive_u8_arithmetic.cpp b/test/test_exhaustive_u8_arithmetic.cpp new file mode 100644 index 0000000..aa8699d --- /dev/null +++ b/test/test_exhaustive_u8_arithmetic.cpp @@ -0,0 +1,371 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// +// Exhaustive verification tests for u8 arithmetic operations. +// Tests all 256 x 256 = 65,536 input combinations for mathematical correctness. +// This provides a rigorous proof that the algorithm logic is correct. + +#include + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include +#include + +#endif + +using namespace boost::safe_numbers; + +// Use u32 as wider type for computing reference values +constexpr std::uint32_t U8_MAX {255U}; + +// ============================================ +// Addition Tests +// ============================================ + +void test_exhaustive_saturating_add() +{ + for (std::uint32_t lhs_val {0}; lhs_val <= U8_MAX; ++lhs_val) + { + for (std::uint32_t rhs_val {0}; rhs_val <= U8_MAX; ++rhs_val) + { + const auto lhs {u8{static_cast(lhs_val)}}; + const auto rhs {u8{static_cast(rhs_val)}}; + + const auto wide_sum {lhs_val + rhs_val}; + const auto expected {static_cast(wide_sum > U8_MAX ? U8_MAX : wide_sum)}; + + const auto result {saturating_add(lhs, rhs)}; + BOOST_TEST_EQ(static_cast(result), expected); + } + } +} + +void test_exhaustive_overflowing_add() +{ + for (std::uint32_t lhs_val {0}; lhs_val <= U8_MAX; ++lhs_val) + { + for (std::uint32_t rhs_val {0}; rhs_val <= U8_MAX; ++rhs_val) + { + const auto lhs {u8{static_cast(lhs_val)}}; + const auto rhs {u8{static_cast(rhs_val)}}; + + const auto wide_sum {lhs_val + rhs_val}; + const auto expected_result {static_cast(wide_sum & 0xFFU)}; + const auto expected_overflow {wide_sum > U8_MAX}; + + const auto [result, overflowed] {overflowing_add(lhs, rhs)}; + BOOST_TEST_EQ(static_cast(result), expected_result); + BOOST_TEST_EQ(overflowed, expected_overflow); + } + } +} + +void test_exhaustive_checked_add() +{ + for (std::uint32_t lhs_val {0}; lhs_val <= U8_MAX; ++lhs_val) + { + for (std::uint32_t rhs_val {0}; rhs_val <= U8_MAX; ++rhs_val) + { + const auto lhs {u8{static_cast(lhs_val)}}; + const auto rhs {u8{static_cast(rhs_val)}}; + + const auto wide_sum {lhs_val + rhs_val}; + const auto should_overflow {wide_sum > U8_MAX}; + + const auto result {checked_add(lhs, rhs)}; + + if (should_overflow) + { + BOOST_TEST(!result.has_value()); + } + else + { + BOOST_TEST(result.has_value()); + BOOST_TEST_EQ(static_cast(result.value()), static_cast(wide_sum)); + } + } + } +} + +void test_exhaustive_wrapping_add() +{ + for (std::uint32_t lhs_val {0}; lhs_val <= U8_MAX; ++lhs_val) + { + for (std::uint32_t rhs_val {0}; rhs_val <= U8_MAX; ++rhs_val) + { + const auto lhs {u8{static_cast(lhs_val)}}; + const auto rhs {u8{static_cast(rhs_val)}}; + + const auto wide_sum {lhs_val + rhs_val}; + const auto expected {static_cast(wide_sum & 0xFFU)}; + + const auto result {wrapping_add(lhs, rhs)}; + BOOST_TEST_EQ(static_cast(result), expected); + } + } +} + +// ============================================ +// Subtraction Tests +// ============================================ + +void test_exhaustive_saturating_sub() +{ + for (std::uint32_t lhs_val {0}; lhs_val <= U8_MAX; ++lhs_val) + { + for (std::uint32_t rhs_val {0}; rhs_val <= U8_MAX; ++rhs_val) + { + const auto lhs {u8{static_cast(lhs_val)}}; + const auto rhs {u8{static_cast(rhs_val)}}; + + const auto expected {static_cast(rhs_val > lhs_val ? 0U : lhs_val - rhs_val)}; + + const auto result {saturating_sub(lhs, rhs)}; + BOOST_TEST_EQ(static_cast(result), expected); + } + } +} + +void test_exhaustive_overflowing_sub() +{ + for (std::uint32_t lhs_val {0}; lhs_val <= U8_MAX; ++lhs_val) + { + for (std::uint32_t rhs_val {0}; rhs_val <= U8_MAX; ++rhs_val) + { + const auto lhs {u8{static_cast(lhs_val)}}; + const auto rhs {u8{static_cast(rhs_val)}}; + + // Wrapping subtraction: (lhs - rhs + 256) % 256 + const auto wide_diff {static_cast(lhs_val) - static_cast(rhs_val)}; + const auto expected_result {static_cast((wide_diff + 256) & 0xFFU)}; + const auto expected_underflow {rhs_val > lhs_val}; + + const auto [result, underflowed] {overflowing_sub(lhs, rhs)}; + BOOST_TEST_EQ(static_cast(result), expected_result); + BOOST_TEST_EQ(underflowed, expected_underflow); + } + } +} + +void test_exhaustive_checked_sub() +{ + for (std::uint32_t lhs_val {0}; lhs_val <= U8_MAX; ++lhs_val) + { + for (std::uint32_t rhs_val {0}; rhs_val <= U8_MAX; ++rhs_val) + { + const auto lhs {u8{static_cast(lhs_val)}}; + const auto rhs {u8{static_cast(rhs_val)}}; + + const auto should_underflow {rhs_val > lhs_val}; + + const auto result {checked_sub(lhs, rhs)}; + + if (should_underflow) + { + BOOST_TEST(!result.has_value()); + } + else + { + BOOST_TEST(result.has_value()); + BOOST_TEST_EQ(static_cast(result.value()), static_cast(lhs_val - rhs_val)); + } + } + } +} + +void test_exhaustive_wrapping_sub() +{ + for (std::uint32_t lhs_val {0}; lhs_val <= U8_MAX; ++lhs_val) + { + for (std::uint32_t rhs_val {0}; rhs_val <= U8_MAX; ++rhs_val) + { + const auto lhs {u8{static_cast(lhs_val)}}; + const auto rhs {u8{static_cast(rhs_val)}}; + + const auto wide_diff {static_cast(lhs_val) - static_cast(rhs_val)}; + const auto expected {static_cast((wide_diff + 256) & 0xFFU)}; + + const auto result {wrapping_sub(lhs, rhs)}; + BOOST_TEST_EQ(static_cast(result), expected); + } + } +} + +// ============================================ +// Multiplication Tests +// ============================================ + +void test_exhaustive_saturating_mul() +{ + for (std::uint32_t lhs_val {0}; lhs_val <= U8_MAX; ++lhs_val) + { + for (std::uint32_t rhs_val {0}; rhs_val <= U8_MAX; ++rhs_val) + { + const auto lhs {u8{static_cast(lhs_val)}}; + const auto rhs {u8{static_cast(rhs_val)}}; + + const auto wide_product {lhs_val * rhs_val}; + const auto expected {static_cast(wide_product > U8_MAX ? U8_MAX : wide_product)}; + + const auto result {saturating_mul(lhs, rhs)}; + BOOST_TEST_EQ(static_cast(result), expected); + } + } +} + +void test_exhaustive_overflowing_mul() +{ + for (std::uint32_t lhs_val {0}; lhs_val <= U8_MAX; ++lhs_val) + { + for (std::uint32_t rhs_val {0}; rhs_val <= U8_MAX; ++rhs_val) + { + const auto lhs {u8{static_cast(lhs_val)}}; + const auto rhs {u8{static_cast(rhs_val)}}; + + const auto wide_product {lhs_val * rhs_val}; + const auto expected_result {static_cast(wide_product & 0xFFU)}; + const auto expected_overflow {wide_product > U8_MAX}; + + const auto [result, overflowed] {overflowing_mul(lhs, rhs)}; + BOOST_TEST_EQ(static_cast(result), expected_result); + BOOST_TEST_EQ(overflowed, expected_overflow); + } + } +} + +void test_exhaustive_checked_mul() +{ + for (std::uint32_t lhs_val {0}; lhs_val <= U8_MAX; ++lhs_val) + { + for (std::uint32_t rhs_val {0}; rhs_val <= U8_MAX; ++rhs_val) + { + const auto lhs {u8{static_cast(lhs_val)}}; + const auto rhs {u8{static_cast(rhs_val)}}; + + const auto wide_product {lhs_val * rhs_val}; + const auto should_overflow {wide_product > U8_MAX}; + + const auto result {checked_mul(lhs, rhs)}; + + if (should_overflow) + { + BOOST_TEST(!result.has_value()); + } + else + { + BOOST_TEST(result.has_value()); + BOOST_TEST_EQ(static_cast(result.value()), static_cast(wide_product)); + } + } + } +} + +void test_exhaustive_wrapping_mul() +{ + for (std::uint32_t lhs_val {0}; lhs_val <= U8_MAX; ++lhs_val) + { + for (std::uint32_t rhs_val {0}; rhs_val <= U8_MAX; ++rhs_val) + { + const auto lhs {u8{static_cast(lhs_val)}}; + const auto rhs {u8{static_cast(rhs_val)}}; + + const auto wide_product {lhs_val * rhs_val}; + const auto expected {static_cast(wide_product & 0xFFU)}; + + const auto result {wrapping_mul(lhs, rhs)}; + BOOST_TEST_EQ(static_cast(result), expected); + } + } +} + +// ============================================ +// Division Tests +// ============================================ + +void test_exhaustive_checked_div() +{ + for (std::uint32_t lhs_val {0}; lhs_val <= U8_MAX; ++lhs_val) + { + for (std::uint32_t rhs_val {0}; rhs_val <= U8_MAX; ++rhs_val) + { + const auto lhs {u8{static_cast(lhs_val)}}; + const auto rhs {u8{static_cast(rhs_val)}}; + + const auto result {checked_div(lhs, rhs)}; + + if (rhs_val == 0U) + { + BOOST_TEST(!result.has_value()); + } + else + { + BOOST_TEST(result.has_value()); + BOOST_TEST_EQ(static_cast(result.value()), static_cast(lhs_val / rhs_val)); + } + } + } +} + +// ============================================ +// Modulo Tests +// ============================================ + +void test_exhaustive_checked_mod() +{ + for (std::uint32_t lhs_val {0}; lhs_val <= U8_MAX; ++lhs_val) + { + for (std::uint32_t rhs_val {0}; rhs_val <= U8_MAX; ++rhs_val) + { + const auto lhs {u8{static_cast(lhs_val)}}; + const auto rhs {u8{static_cast(rhs_val)}}; + + const auto result {checked_mod(lhs, rhs)}; + + if (rhs_val == 0U) + { + BOOST_TEST(!result.has_value()); + } + else + { + BOOST_TEST(result.has_value()); + BOOST_TEST_EQ(static_cast(result.value()), static_cast(lhs_val % rhs_val)); + } + } + } +} + +int main() +{ + // Addition: 4 policies tested exhaustively + test_exhaustive_saturating_add(); + test_exhaustive_overflowing_add(); + test_exhaustive_checked_add(); + test_exhaustive_wrapping_add(); + + // Subtraction: 4 policies tested exhaustively + test_exhaustive_saturating_sub(); + test_exhaustive_overflowing_sub(); + test_exhaustive_checked_sub(); + test_exhaustive_wrapping_sub(); + + // Multiplication: 4 policies tested exhaustively + test_exhaustive_saturating_mul(); + test_exhaustive_overflowing_mul(); + test_exhaustive_checked_mul(); + test_exhaustive_wrapping_mul(); + + // Division: checked policy tested exhaustively (others throw on div by zero) + test_exhaustive_checked_div(); + + // Modulo: checked policy tested exhaustively (others throw on mod by zero) + test_exhaustive_checked_mod(); + + return boost::report_errors(); +}