1
0

feat: add strict_* family for safe numeric op.

- add strict_* function family for same numeric operation. this function family recently become stable in Rust.
- add corresponding test.
This commit is contained in:
2025-12-19 09:44:22 +08:00
parent fece224ec5
commit 5993ae59c0
2 changed files with 158 additions and 1 deletions

View File

@@ -32,7 +32,7 @@
* @details
* After writing some programs in Rust, I've deeply realized the richness of operators for primitive types in Rust.
* You can explicitly specify the behavior of arithmetic overflow
* (choose one of wrapping, checked, overflowing, and saturating).
* (choose one of wrapping, checked, overflowing, saturating and strict).
* Therefore, I'm replicating these convenient features from Rust in this namespace.
*
* Additionally, I provide a bunch of extra operations, called ordinary operation.
@@ -481,6 +481,80 @@ namespace yycc::num::safe_op {
#pragma endregion
#pragma region Strict operations
// YYC MARK:
// If overflow occurs when using strict_* function family,
// these functions will throw exception, otherwise return the computed result.
/**
* @brief Performs a strict addition operation on two integers.
* @tparam T Integer type.
* @param[in] a The left operand of the addition.
* @param[in] b The right operand of the addition.
* @return The result if no overflow occurs.
* @exception std::overflow_error Overflow occurs when perform operation.
*/
template<typename T>
requires std::integral<T>
std::optional<T> strict_add(T a, T b) {
T result;
if (hardware_add_overflow(a, b, &result)) throw std::overflow_error("overflow or underflow in strict_add");
return result;
}
/**
* @brief Performs a strict subtraction operation on two integers.
* @tparam T Integer type.
* @param[in] a The left operand of the subtraction.
* @param[in] b The right operand of the subtraction.
* @return The result if no overflow occurs.
* @exception std::overflow_error Overflow occurs when perform operation.
*/
template<typename T>
requires std::integral<T>
std::optional<T> strict_sub(T a, T b) {
T result;
if (hardware_sub_overflow(a, b, &result)) throw std::overflow_error("overflow or underflow in strict_sub");
return result;
}
/**
* @brief Performs a strict multiplication operation on two integers.
* @tparam T Integer type.
* @param[in] a The left operand of the multiplication.
* @param[in] b The right operand of the multiplication.
* @return The result if no overflow occurs.
* @exception std::overflow_error Overflow occurs when perform operation.
*/
template<typename T>
requires std::integral<T>
std::optional<T> strict_mul(T a, T b) {
T result;
if (hardware_mul_overflow(a, b, &result)) throw std::overflow_error("overflow or underflow in strict_mul");
return result;
}
/**
* @brief Performs a strict division operation on two integers.
* @tparam T Integer type.
* @param[in] a The left operand of the division.
* @param[in] b The right operand of the division.
* @return The result if no error occurs.
* @exception std::overflow_error Overflow or division by zero occurs when perform operation.
*/
template<typename T>
requires std::integral<T>
std::optional<T> strict_div(T a, T b) {
// Division by zero is undefined behavior.
if (ub_div_zero(a, b)) throw std::overflow_error("division by zero in strict_div");
// `INT_MIN / -1` overflow undefined behavior.
if (ub_signed_int_min_div_minus_one(a, b)) throw std::overflow_error("overflow or underflow in strict_div");
return a / b;
}
#pragma endregion
#pragma region Overflowing operations
// YYC MARK:

View File

@@ -155,6 +155,89 @@ namespace yycctest::num::safe_op {
#pragma endregion
#pragma region Strict operations
TEST(NumSafeOp, StrictAdd) {
// Unsigned
{
auto rv = OP::strict_add<u32>(MAX<u32> - 2, 1);
EXPECT_EQ(rv, MAX<u32> - 1);
}
{
EXPECT_ANY_THROW(OP::strict_add<u32>(MAX<u32> - 2, 3));
}
// Signed
{
auto rv = OP::strict_add<i32>(MAX<i32> - 2, 1);
EXPECT_EQ(rv, MAX<i32> - 1);
}
{
EXPECT_ANY_THROW(OP::strict_add<i32>(MAX<i32> - 2, 3));
}
}
TEST(NumSafeOp, StrictSub) {
// Unsigned
{
auto rv = OP::strict_sub<u32>(1, 1);
EXPECT_EQ(rv, 0);
}
{
EXPECT_ANY_THROW(OP::strict_sub<u32>(0, 1));
}
// Signed
{
auto rv = OP::strict_sub<i32>(MIN<i32> + 2, 1);
EXPECT_EQ(rv, MIN<i32> + 1);
}
{
EXPECT_ANY_THROW(OP::strict_sub<i32>(MIN<i32> + 2, 3));
}
}
TEST(NumSafeOp, StrictMul) {
// Unsigned
{
auto rv = OP::strict_mul<u32>(5, 1);
EXPECT_EQ(rv, 5);
}
{
EXPECT_ANY_THROW(OP::strict_mul<u32>(MAX<u32>, 2));
}
// Signed
{
auto rv = OP::strict_mul<i32>(MAX<i32>, 1);
EXPECT_EQ(rv, MAX<i32>);
}
{
EXPECT_ANY_THROW(OP::strict_mul<i32>(MAX<i32>, 2));
}
}
TEST(NumSafeOp, StrictDiv) {
// Unsigned
{
auto rv = OP::strict_div<u32>(128, 2);
EXPECT_EQ(rv, 64);
}
{
EXPECT_ANY_THROW(OP::strict_div<u32>(1, 0));
}
// Signed
{
auto rv = OP::strict_div<i32>(MIN<i32> + 1, -1);
EXPECT_EQ(rv, INT32_C(2147483647));
}
{
EXPECT_ANY_THROW(OP::strict_div<i32>(MIN<i32>, -1));
}
{
EXPECT_ANY_THROW(OP::strict_div<i32>(1, 0));
}
}
#pragma endregion
#pragma region Overflowing operations
TEST(NumSafeOp, OverflowingAdd) {