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:
@@ -32,7 +32,7 @@
|
|||||||
* @details
|
* @details
|
||||||
* After writing some programs in Rust, I've deeply realized the richness of operators for primitive types in Rust.
|
* 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
|
* 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.
|
* Therefore, I'm replicating these convenient features from Rust in this namespace.
|
||||||
*
|
*
|
||||||
* Additionally, I provide a bunch of extra operations, called ordinary operation.
|
* Additionally, I provide a bunch of extra operations, called ordinary operation.
|
||||||
@@ -481,6 +481,80 @@ namespace yycc::num::safe_op {
|
|||||||
|
|
||||||
#pragma endregion
|
#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
|
#pragma region Overflowing operations
|
||||||
|
|
||||||
// YYC MARK:
|
// YYC MARK:
|
||||||
|
|||||||
@@ -155,6 +155,89 @@ namespace yycctest::num::safe_op {
|
|||||||
|
|
||||||
#pragma endregion
|
#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
|
#pragma region Overflowing operations
|
||||||
|
|
||||||
TEST(NumSafeOp, OverflowingAdd) {
|
TEST(NumSafeOp, OverflowingAdd) {
|
||||||
|
|||||||
Reference in New Issue
Block a user