diff --git a/doc/modules/ROOT/pages/cstdlib.adoc b/doc/modules/ROOT/pages/cstdlib.adoc index b3a6ea8f..ed5636cc 100644 --- a/doc/modules/ROOT/pages/cstdlib.adoc +++ b/doc/modules/ROOT/pages/cstdlib.adoc @@ -59,4 +59,6 @@ BOOST_INT128_HOST_DEVICE constexpr i128div_t div(int128_t lhs, int128_t rhs) noe } // namespace boost ---- -The result of `quot` and `rem` from this function, including the sign, are the same as if you performed division and modulo separately. +For any non-zero divisor the `quot` and `rem` values, including their sign, are the same as if you performed division and modulo separately. + +Division by zero is handled differently from the `operator/` and `operator%` operators. Those operators leave a zero divisor undefined (matching the built-in `__int128` types), whereas `div` detects a zero divisor and returns a zero-initialized result (`quot == 0` and `rem == 0`) instead. A zero numerator with a non-zero divisor likewise returns `{0, 0}`. diff --git a/doc/modules/ROOT/pages/int128_t.adoc b/doc/modules/ROOT/pages/int128_t.adoc index 7a9d484c..2b84ba92 100644 --- a/doc/modules/ROOT/pages/int128_t.adoc +++ b/doc/modules/ROOT/pages/int128_t.adoc @@ -71,6 +71,8 @@ Specifically: * For shift operators, the result type follows the LHS: `int128_t << T` and `int128_t >> T` always return `int128_t`, regardless of `T`. * All comparison operators return `bool`, with the comparison performed on the operands after conversion to the common type. +NOTE: Division and remainder match `__int128` for every input except the two the built-in leaves undefined. Division or remainder by zero is likewise undefined behavior: the library adds no zero-divisor check, so it behaves like the built-in (a hardware trap on platforms that fault on integer division by zero) and is a hard compile-time error in a constant expression. The signed overflow `INT128_MIN / -1`, whose true quotient is not representable, is the one case the library defines rather than leaving undefined: it wraps to `INT128_MIN` (and `INT128_MIN % -1` is `0`), matching the two's-complement wrap the library applies to other overflowing signed operations such as `INT128_MIN * -1`. See xref:int128_t.adoc#i128_math_operators[Division] and Modulo below. + See xref:mixed_type_ops.adoc[Mixed Type Operations] for the full set of cross-type signatures and detailed result-type rules. [#i128_constructors] @@ -490,6 +492,9 @@ BOOST_INT128_HOST_DEVICE constexpr int128_t operator/(const int128_t lhs, const ---- Returns as an `int128_t` the quotient of `lhs` and `rhs` without exception. +The quotient truncates toward zero, and for any `rhs` other than `0` (and other than the overflow noted below) the result is identical to the built-in `__int128` division. +Division by zero is undefined behavior, exactly as for `__int128`: the library adds no zero-divisor check (so the generated code matches the built-in and traps where the hardware faults on integer division by zero), and a zero divisor seen during constant evaluation is a hard compile-time error. +The signed overflow `INT128_MIN / -1` is defined to wrap to `INT128_MIN` rather than being left undefined as it is for the built-in. This operation is only defined for integers and is subject to mixed sign limitations discussed xref:int128_t.adoc#i128_operator_behavior[above]. === Modulo @@ -506,6 +511,9 @@ BOOST_INT128_HOST_DEVICE constexpr int128_t operator%(const int128_t lhs, const ---- Returns as an `int128_t` the remainder of `lhs` and `rhs` without exception. +The remainder takes the sign of the dividend `lhs`, and for any `rhs` other than `0` the result is identical to the built-in `__int128` remainder, so that `(lhs / rhs) * rhs + lhs % rhs == lhs`. +Remainder by zero is undefined behavior, exactly as for `__int128`: the library adds no zero-divisor check (so the generated code matches the built-in and traps where the hardware faults on integer division by zero), and a zero divisor seen during constant evaluation is a hard compile-time error. +The signed overflow `INT128_MIN % -1` is defined to be `0` rather than being left undefined as it is for the built-in. This operation is only defined for integers and is subject to mixed sign limitations discussed xref:int128_t.adoc#i128_operator_behavior[above]. [#i128_abs] diff --git a/doc/modules/ROOT/pages/numeric.adoc b/doc/modules/ROOT/pages/numeric.adoc index 0e5653a9..51dea723 100644 --- a/doc/modules/ROOT/pages/numeric.adoc +++ b/doc/modules/ROOT/pages/numeric.adoc @@ -44,6 +44,9 @@ BOOST_INT128_HOST_DEVICE constexpr int128_t div_sat(int128_t lhs, int128_t rhs) ---- +For `div_sat` the only value that can overflow is the signed `INT128_MIN / -1`, which saturates to `INT128_MAX`; every other non-zero divisor gives the same result as the plain `operator/`. +Division by zero is a precondition violation and therefore undefined behavior (the divisor must be non-zero), exactly as for the built-in `__int128` types and the plain division operator; `div_sat` adds no zero-divisor check. + [#saturating_cast] == Saturating Cast diff --git a/doc/modules/ROOT/pages/uint128_t.adoc b/doc/modules/ROOT/pages/uint128_t.adoc index 6ffa7472..dbc620ab 100644 --- a/doc/modules/ROOT/pages/uint128_t.adoc +++ b/doc/modules/ROOT/pages/uint128_t.adoc @@ -72,6 +72,8 @@ Specifically: For example, `uint128_t{5} > -1` returns `false` because the signed `-1` converts to a value greater than `5` under unsigned 128-bit arithmetic, exactly as `(unsigned __int128){5} > -1` would. +NOTE: The results match `unsigned __int128` for every input with a non-zero divisor. Division or remainder by zero is the one exception, and it is handled exactly as the built-in type handles it: as undefined behavior. The library performs no zero-divisor check, so a `uint128_t` divided or taken modulo by zero behaves like the built-in operation (a hardware trap on platforms that fault on integer division by zero), and in a constant expression it is a hard compile-time error just as `1u / 0u` is. See xref:uint128_t.adoc#u128_math_operators[Division] and Modulo below. + See xref:mixed_type_ops.adoc[Mixed Type Operations] for the full set of cross-type signatures and detailed result-type rules. [#u128_constructors] @@ -486,6 +488,8 @@ BOOST_INT128_HOST_DEVICE constexpr uint128_t operator/(const uint128_t lhs, cons ---- Returns as a `uint128_t` the quotient of `lhs` and `rhs` without exception. +For any non-zero `rhs` the result is identical to the built-in `unsigned __int128` division. +Division by zero is undefined behavior, exactly as for `unsigned __int128`: the library adds no zero-divisor check (so the generated code matches the built-in and traps where the hardware faults on integer division by zero), and a zero divisor seen during constant evaluation is a hard compile-time error. This operation is only defined for integers and is subject to mixed sign limitations discussed xref:uint128_t.adoc#u128_operator_behavior[above]. === Modulo @@ -502,6 +506,8 @@ BOOST_INT128_HOST_DEVICE constexpr uint128_t operator%(const uint128_t lhs, cons ---- Returns as a `uint128_t` the remainder of `lhs` and `rhs` without exception. +For any non-zero `rhs` the result is identical to the built-in `unsigned __int128` remainder. +Remainder by zero is undefined behavior, exactly as for `unsigned __int128`: the library adds no zero-divisor check (so the generated code matches the built-in and traps where the hardware faults on integer division by zero), and a zero divisor seen during constant evaluation is a hard compile-time error. This operation is only defined for integers and is subject to mixed sign limitations discussed xref:uint128_t.adoc#u128_operator_behavior[above]. [#u128_limits] diff --git a/include/boost/int128/detail/int128_imp.hpp b/include/boost/int128/detail/int128_imp.hpp index 35c1f2ad..a363fe23 100644 --- a/include/boost/int128/detail/int128_imp.hpp +++ b/include/boost/int128/detail/int128_imp.hpp @@ -2207,7 +2207,8 @@ BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr int128_t operator/(const { if (BOOST_INT128_UNLIKELY(rhs == 0)) { - return {0, 0}; + // Division or remainder by zero is undefined behavior for the builtin __int128 types (a hardware trap). We match that: marking it unreachable keeps codegen branch-free and vectorizable. + BOOST_INT128_UNREACHABLE; } constexpr int128_t min_val {INT64_MIN, 0}; @@ -2259,7 +2260,8 @@ BOOST_INT128_HOST_DEVICE constexpr int128_t operator/(const int128_t lhs, const if (BOOST_INT128_UNLIKELY(rhs == 0)) { - return {0, 0}; + // Division or remainder by zero is undefined behavior for the builtin __int128 types (a hardware trap). We match that: marking it unreachable keeps codegen branch-free and vectorizable. + BOOST_INT128_UNREACHABLE; } const auto abs_lhs {abs(lhs)}; @@ -2274,7 +2276,8 @@ BOOST_INT128_HOST_DEVICE constexpr int128_t operator/(const UnsignedInteger lhs, { if (BOOST_INT128_UNLIKELY(rhs == 0)) { - return {0, 0}; + // Division or remainder by zero is undefined behavior for the builtin __int128 types (a hardware trap). We match that: marking it unreachable keeps codegen branch-free and vectorizable. + BOOST_INT128_UNREACHABLE; } if (rhs.high != 0 && rhs.high != -1) @@ -2307,7 +2310,8 @@ BOOST_INT128_HOST_DEVICE constexpr int128_t operator/(const int128_t lhs, const if (BOOST_INT128_UNLIKELY(rhs == 0)) { - return {0, 0}; + // Division or remainder by zero is undefined behavior for the builtin __int128 types (a hardware trap). We match that: marking it unreachable keeps codegen branch-free and vectorizable. + BOOST_INT128_UNREACHABLE; } int128_t quotient {}; @@ -2333,7 +2337,8 @@ BOOST_INT128_HOST_DEVICE constexpr int128_t operator/(const SignedInteger lhs, c { if (BOOST_INT128_UNLIKELY(rhs == 0)) { - return {0, 0}; + // Division or remainder by zero is undefined behavior for the builtin __int128 types (a hardware trap). We match that: marking it unreachable keeps codegen branch-free and vectorizable. + BOOST_INT128_UNREACHABLE; } if (rhs.high != 0 && rhs.high != -1) @@ -2444,7 +2449,8 @@ BOOST_INT128_HOST_DEVICE constexpr int128_t operator%(const int128_t lhs, const if (BOOST_INT128_UNLIKELY(rhs == 0)) { - return {0, 0}; + // Division or remainder by zero is undefined behavior for the builtin __int128 types (a hardware trap). We match that: marking it unreachable keeps codegen branch-free and vectorizable. + BOOST_INT128_UNREACHABLE; } int128_t quotient {}; @@ -2464,7 +2470,8 @@ BOOST_INT128_HOST_DEVICE constexpr int128_t operator%(const UnsignedInteger lhs, if (BOOST_INT128_UNLIKELY(rhs == 0)) { - return {0, 0}; + // Division or remainder by zero is undefined behavior for the builtin __int128 types (a hardware trap). We match that: marking it unreachable keeps codegen branch-free and vectorizable. + BOOST_INT128_UNREACHABLE; } const auto abs_rhs {abs(rhs)}; @@ -2495,7 +2502,8 @@ BOOST_INT128_HOST_DEVICE constexpr int128_t operator%(const int128_t lhs, const { if (rhs == 0) { - return {0, 0}; + // Division or remainder by zero is undefined behavior for the builtin __int128 types (a hardware trap). We match that: marking it unreachable keeps codegen branch-free and vectorizable. + BOOST_INT128_UNREACHABLE; } constexpr int128_t min_val {INT64_MIN, 0}; diff --git a/include/boost/int128/detail/uint128_imp.hpp b/include/boost/int128/detail/uint128_imp.hpp index 5cfeeb6f..558bc340 100644 --- a/include/boost/int128/detail/uint128_imp.hpp +++ b/include/boost/int128/detail/uint128_imp.hpp @@ -2449,7 +2449,8 @@ BOOST_INT128_HOST_DEVICE constexpr uint128_t operator/(const uint128_t lhs, cons if (BOOST_INT128_UNLIKELY(rhs == 0U)) { - return {0, 0}; + // Division or remainder by zero is undefined behavior for the builtin __int128 types (a hardware trap). We match that: marking it unreachable keeps codegen branch-free and vectorizable. + BOOST_INT128_UNREACHABLE; } if (lhs < rhs) @@ -2471,7 +2472,8 @@ BOOST_INT128_HOST_DEVICE constexpr uint128_t operator/(const UnsignedInteger lhs if (BOOST_INT128_UNLIKELY(rhs == 0U)) { - return {0, 0}; + // Division or remainder by zero is undefined behavior for the builtin __int128 types (a hardware trap). We match that: marking it unreachable keeps codegen branch-free and vectorizable. + BOOST_INT128_UNREACHABLE; } if (lhs < rhs) @@ -2486,7 +2488,8 @@ BOOST_INT128_HOST_DEVICE constexpr uint128_t operator/(const uint128_t lhs, cons { if (BOOST_INT128_UNLIKELY(rhs == 0U)) { - return {0, 0}; + // Division or remainder by zero is undefined behavior for the builtin __int128 types (a hardware trap). We match that: marking it unreachable keeps codegen branch-free and vectorizable. + BOOST_INT128_UNREACHABLE; } if (lhs < rhs) @@ -2610,7 +2613,8 @@ BOOST_INT128_HOST_DEVICE constexpr uint128_t operator%(const uint128_t lhs, cons if (BOOST_INT128_UNLIKELY(rhs == 0U)) { - return {0, 0}; + // Division or remainder by zero is undefined behavior for the builtin __int128 types (a hardware trap). We match that: marking it unreachable keeps codegen branch-free and vectorizable. + BOOST_INT128_UNREACHABLE; } if (lhs.high != 0) @@ -2635,7 +2639,8 @@ BOOST_INT128_HOST_DEVICE constexpr uint128_t operator%(const UnsignedInteger lhs if (BOOST_INT128_UNLIKELY(rhs == 0U)) { - return {0, 0}; + // Division or remainder by zero is undefined behavior for the builtin __int128 types (a hardware trap). We match that: marking it unreachable keeps codegen branch-free and vectorizable. + BOOST_INT128_UNREACHABLE; } else if (rhs > lhs) { @@ -2649,7 +2654,8 @@ BOOST_INT128_HOST_DEVICE constexpr uint128_t operator%(const uint128_t lhs, cons { if (BOOST_INT128_UNLIKELY(rhs == 0U)) { - return {0, 0}; + // Division or remainder by zero is undefined behavior for the builtin __int128 types (a hardware trap). We match that: marking it unreachable keeps codegen branch-free and vectorizable. + BOOST_INT128_UNREACHABLE; } if (rhs > lhs) { diff --git a/test/test_i128.cpp b/test/test_i128.cpp index 5dd982ff..608fac83 100644 --- a/test/test_i128.cpp +++ b/test/test_i128.cpp @@ -944,10 +944,7 @@ void test_operator_div() const boost::int128::int128_t check_2_value {value2}; BOOST_TEST(check_1_value == (emulated_value / check_2_value)); - // Shouldn't crash - BOOST_TEST(check_2_value / IntType(0) == 0); - BOOST_TEST(check_2_value / static_cast(0) == 0); - BOOST_TEST(value / static_cast(0) == 0); + // Division by zero is undefined behavior (matching the builtin __int128 types), so it is not tested. // Always 0 BOOST_TEST(small_emulated_value / emulated_value == 0); @@ -1063,11 +1060,7 @@ void test_operator_mod() const boost::int128::int128_t check_2_value {value2}; BOOST_TEST(check_1_value == (emulated_value % check_2_value)); - // Shouldn't crash - // Codecov has these marked as partials which is as long as they pass is irrelevant - BOOST_TEST(check_2_value % IntType(0) == 0); // LCOV_EXCL_LINE - BOOST_TEST(value % static_cast(0) == 0); // LCOV_EXCL_LINE - BOOST_TEST(small_emulated_value % static_cast(0) == 0); // LCOV_EXCL_LINE + // Remainder by zero is undefined behavior (matching the builtin __int128 types), so it is not tested. BOOST_TEST(small_emulated_value % emulated_value == small_emulated_value); // LCOV_EXCL_LINE BOOST_TEST(small_emulated_value % small_emulated_value == 0); // LCOV_EXCL_LINE } diff --git a/test/test_u128.cpp b/test/test_u128.cpp index 925f2c73..6d219560 100644 --- a/test/test_u128.cpp +++ b/test/test_u128.cpp @@ -1133,9 +1133,7 @@ void test_operator_div() const boost::int128::uint128_t check_2_value {value2}; BOOST_TEST(check_1_value == (emulated_value / check_2_value)); - // Shouldn't crash - BOOST_TEST(check_2_value / IntType(0) == 0); - BOOST_TEST(value / static_cast(0) == 0); + // Division by zero is undefined behavior (matching the builtin __int128 types), so it is not tested. // Always 0 BOOST_TEST(small_emulated_value / emulated_value == 0); @@ -1235,10 +1233,7 @@ void test_operator_mod() const boost::int128::uint128_t check_2_value {value2}; BOOST_TEST(check_1_value == (emulated_value % check_2_value)); - // Shouldn't crash - BOOST_TEST(check_2_value % IntType(0) == 0); - BOOST_TEST(value % static_cast(0) == 0); - BOOST_TEST(small_emulated_value % static_cast(0) == 0); + // Remainder by zero is undefined behavior (matching the builtin __int128 types), so it is not tested. BOOST_TEST(small_emulated_value % emulated_value == small_emulated_value); BOOST_TEST(small_emulated_value % small_emulated_value == 0);