// The MIT License (MIT) // // Copyright (c) 2018 Mateusz Pusz // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #include #include #include #include #include #include #include #ifdef MP_UNITS_IMPORT_STD import std; #else #include #include #include #include #endif using namespace mp_units; using namespace mp_units::utility; using namespace mp_units::si::unit_symbols; using namespace mp_units::yard_pound::unit_symbols; // ============================================================================ // safe_int with throw_policy (default on hosted) // ============================================================================ TEST_CASE("safe_int with throw_policy", "[safe_int][throw]") { using T = safe_int; const auto int_max = std::numeric_limits::max(); const auto int_min = std::numeric_limits::min(); SECTION("normal arithmetic does not throw") { REQUIRE(T{3} + T{4} == T{7}); REQUIRE(T{10} - T{3} == T{7}); REQUIRE(T{3} * T{4} == T{12}); REQUIRE(T{12} / T{4} == T{3}); REQUIRE(T{10} % T{3} == T{1}); REQUIRE(-T{5} == T{-5}); T x{5}; REQUIRE(++x == T{6}); REQUIRE(x++ == T{6}); REQUIRE(x == T{7}); REQUIRE(--x == T{6}); REQUIRE(x-- == T{6}); REQUIRE(x == T{5}); } SECTION("addition overflow throws") { REQUIRE_THROWS_AS(T{int_max} + T{1}, std::overflow_error); REQUIRE_THROWS_AS(T{int_min} + T{-1}, std::overflow_error); } SECTION("subtraction overflow throws") { REQUIRE_THROWS_AS(T{int_min} - T{1}, std::overflow_error); REQUIRE_THROWS_AS(T{int_max} - T{-1}, std::overflow_error); } SECTION("multiplication overflow throws") { REQUIRE_THROWS_AS(T{int_max} * T{2}, std::overflow_error); REQUIRE_THROWS_AS(T{int_min} * T{-1}, std::overflow_error); } SECTION("division overflow throws") { REQUIRE_THROWS_AS(T{1} / T{0}, std::overflow_error); REQUIRE_THROWS_AS(T{int_min} / T{-1}, std::overflow_error); } SECTION("modulo by zero throws") { REQUIRE_THROWS_AS(T{10} % T{0}, std::overflow_error); } SECTION("negation overflow throws") { REQUIRE_THROWS_AS(-T{int_min}, std::overflow_error); } SECTION("negation of short INT16_MIN does not overflow (promotes to int)") { // -(-32768) as short would overflow short, but unary - promotes to int first, // so the result is int(32768) which is in range — no exception expected. using S = safe_int; REQUIRE(-S{std::numeric_limits::min()} == safe_int{32768}); } SECTION("increment overflow throws") { T x{int_max}; REQUIRE_THROWS_AS(++x, std::overflow_error); } SECTION("decrement overflow throws") { T x{int_min}; REQUIRE_THROWS_AS(--x, std::overflow_error); } SECTION("unsigned overflow throws") { using U = safe_int; const auto uint_max = std::numeric_limits::max(); REQUIRE_THROWS_AS(U{uint_max} + U{1u}, std::overflow_error); REQUIRE_THROWS_AS(U{0u} - U{1u}, std::overflow_error); REQUIRE_THROWS_AS(U{uint_max} * U{2u}, std::overflow_error); } SECTION("throw message contains 'overflow'") { try { [[maybe_unused]] auto r = T{int_max} + T{1}; FAIL("expected exception not thrown"); } catch (const std::overflow_error& e) { REQUIRE(std::string(e.what()).find("overflow") != std::string::npos); } } } // ============================================================================ // Converting constructor overflow detection // ============================================================================ TEST_CASE("safe_int converting constructor", "[safe_int][convert][throw]") { SECTION("raw integer narrowing overflow") { // int → short: value too large REQUIRE_THROWS_AS(safe_int(int{40'000}), std::overflow_error); REQUIRE_THROWS_AS(safe_int(int{-40'000}), std::overflow_error); // int → short: value fits — no throw REQUIRE(safe_int(int{100}).value() == 100); REQUIRE(safe_int(int{-100}).value() == -100); REQUIRE(safe_int(int{32'767}).value() == 32'767); REQUIRE(safe_int(int{-32'768}).value() == -32'768); // unsigned → short: value too large REQUIRE_THROWS_AS(safe_int(40'000u), std::overflow_error); // int → uint8_t: value out of range REQUIRE_THROWS_AS(safe_int(int{256}), std::overflow_error); REQUIRE_THROWS_AS(safe_int(int{-1}), std::overflow_error); // int → unsigned: negative value REQUIRE_THROWS_AS(safe_int(int{-1}), std::overflow_error); // unsigned → int: value too large REQUIRE_THROWS_AS(safe_int(std::numeric_limits::max()), std::overflow_error); } SECTION("safe_int narrowing overflow") { // safe_int → safe_int: value too large REQUIRE_THROWS_AS(safe_int(safe_int{40'000}), std::overflow_error); REQUIRE_THROWS_AS(safe_int(safe_int{-40'000}), std::overflow_error); // safe_int → safe_int: value fits — no throw REQUIRE(safe_int(safe_int{100}).value() == 100); // safe_int → safe_int: value too large REQUIRE_THROWS_AS(safe_int(safe_int{std::numeric_limits::max()}), std::overflow_error); // safe_int → safe_int: negative value REQUIRE_THROWS_AS(safe_int(safe_int{-1}), std::overflow_error); } } // ============================================================================ // safe_int as a quantity representation type // ============================================================================ TEST_CASE("safe_int as quantity representation", "[safe_int][quantity]") { SECTION("basic arithmetic does not throw") { quantity> d1 = safe_int{100} * m; quantity> d2 = safe_int{50} * m; REQUIRE((d1 + d2).numerical_value_in(m) == 150); REQUIRE((d1 - d2).numerical_value_in(m) == 50); REQUIRE((d1 * 2).numerical_value_in(m) == 200); REQUIRE((d1 / 2).numerical_value_in(m) == 50); } SECTION("unit conversion does not throw when safe") { quantity> distance = safe_int{2} * m; quantity> distance_mm = distance; REQUIRE(distance_mm.numerical_value_in(mm) == 2000); } SECTION("arithmetic overflow throws") { quantity> large = safe_int{std::numeric_limits::max()} * m; REQUIRE_THROWS_AS(large + safe_int{1} * m, std::overflow_error); } SECTION("cross-unit arithmetic") { quantity> meters = safe_int{20} * m; quantity> feet = safe_int{100} * ft; auto total = meters + feet; REQUIRE(total.force_in(m).numerical_value_in(m) > 0); } SECTION("cross-unit overflow throws") { quantity> meters = safe_int{30'000} * m; quantity> feet = safe_int{10'000} * ft; // Adding meters + feet works (promoted to safe_int) auto total = meters + feet; REQUIRE(total.force_in(m).numerical_value_in(m) > 0); // But forcing back to int16_t overflows (30000 + ~3048 = ~33048 > 32767) REQUIRE_THROWS_AS(total.force_in>(m), std::overflow_error); } SECTION("multiplication overflow throws") { quantity> dist = safe_int{100'000} * m; quantity> width = safe_int{100'000} * m; REQUIRE_THROWS_AS(dist * width, std::overflow_error); } SECTION("scalar multiplication overflow throws") { quantity> dist = safe_int{std::numeric_limits::max()} * m; REQUIRE_THROWS_AS(dist * 2, std::overflow_error); } SECTION(".in(unit) overflow throws") { // 2,200,000 m → µm requires ×1,000,000; product 2.2×10¹² overflows int32_t quantity dist = safe_i32{2'200'000} * m; REQUIRE_THROWS_AS(dist.in(um), std::overflow_error); // sanity: a value that fits must not throw quantity small = safe_i32{2} * m; REQUIRE_NOTHROW(small.in(um)); REQUIRE(small.in(um).numerical_value_in(um) == 2'000'000); } SECTION("automatic common-unit scaling overflow throws") { // 1,500,000,000 m + 1,000,000 km: converting km→common-unit requires ×10⁶ which overflows int32_t quantity dist_m = safe_i32{1'500'000'000} * m; quantity dist_km = safe_i32{1'000'000} * km; REQUIRE_THROWS_AS(dist_m + dist_km, std::overflow_error); // sanity: values that don't overflow must not throw quantity small_m = safe_i32{100} * m; quantity small_km = safe_i32{1} * km; REQUIRE_NOTHROW(small_m + small_km); } SECTION("convenience aliases work") { quantity dist = safe_i32{100} * m; REQUIRE(dist.numerical_value_in(m) == 100); quantity time = safe_i16{10} * s; auto speed = dist / time; REQUIRE(speed.numerical_value_in(m / s) == 10); } SECTION("comparison operations work") { quantity> d1 = safe_int{100} * m; quantity> d2 = safe_int{50} * m; REQUIRE(d1 > d2); REQUIRE(d2 < d1); REQUIRE(d1 >= safe_int{100} * m); REQUIRE(d2 <= safe_int{100} * m); REQUIRE(d1 == safe_int{100} * m); REQUIRE(d1 != d2); } } // ============================================================================ // Scalar op overflow: safe_int op U and U op safe_int // ============================================================================ TEST_CASE("safe_int * scalar operations", "[safe_int][scalar][throw]") { using T = safe_int; const auto int_max = std::numeric_limits::max(); const auto int_min = std::numeric_limits::min(); SECTION("addition overflow throws") { REQUIRE_THROWS_AS(T{int_max} + 1, std::overflow_error); REQUIRE_THROWS_AS(1 + T{int_max}, std::overflow_error); REQUIRE_THROWS_AS(T{int_min} + (-1), std::overflow_error); REQUIRE_THROWS_AS((-1) + T{int_min}, std::overflow_error); } SECTION("subtraction overflow throws") { REQUIRE_THROWS_AS(T{int_min} - 1, std::overflow_error); // INT_MIN - 1 underflows REQUIRE_THROWS_AS(1 - T{int_min}, std::overflow_error); // 1 - INT_MIN = INT_MAX+2 overflows REQUIRE_THROWS_AS(T{int_max} - (-1), std::overflow_error); // INT_MAX - (-1) = INT_MAX+1 overflows REQUIRE_THROWS_AS((-2) - T{int_max}, std::overflow_error); // -2 - INT_MAX < INT_MIN overflows } SECTION("multiplication overflow throws") { REQUIRE_THROWS_AS(T{int_max} * 2, std::overflow_error); REQUIRE_THROWS_AS(2 * T{int_max}, std::overflow_error); } SECTION("division overflow throws") { REQUIRE_THROWS_AS(T{1} / 0, std::overflow_error); // divide by zero REQUIRE_THROWS_AS(1 / T{0}, std::overflow_error); // divide by zero REQUIRE_THROWS_AS(T{int_min} / (-1), std::overflow_error); // INT_MIN / -1 REQUIRE_THROWS_AS(int_min / T{-1}, std::overflow_error); // INT_MIN / -1 } SECTION("modulo by zero throws") { REQUIRE_THROWS_AS(T{10} % 0, std::overflow_error); REQUIRE_THROWS_AS(10 % T{0}, std::overflow_error); } } // ============================================================================ // Cross-wrapper overflow: constrained op safe_int // The integral-winner overloads delegate to safe_int ops, so overflow is detected. // ============================================================================ TEST_CASE("safe_int * constrained operations", "[safe_int][cross-wrapper][throw]") { using C = constrained; using S = safe_int; const auto int_max = std::numeric_limits::max(); const auto int_min = std::numeric_limits::min(); SECTION("addition overflow throws") { REQUIRE_THROWS_AS(C{int_max} + S{1}, std::overflow_error); REQUIRE_THROWS_AS(S{int_max} + C{1}, std::overflow_error); } SECTION("subtraction overflow throws") { REQUIRE_THROWS_AS(C{int_min} - S{1}, std::overflow_error); REQUIRE_THROWS_AS(S{int_min} - C{1}, std::overflow_error); } SECTION("multiplication overflow throws") { REQUIRE_THROWS_AS(C{int_max} * S{2}, std::overflow_error); REQUIRE_THROWS_AS(S{int_max} * C{2}, std::overflow_error); } SECTION("division overflow throws") { REQUIRE_THROWS_AS(C{1} / S{0}, std::overflow_error); REQUIRE_THROWS_AS(S{1} / C{0}, std::overflow_error); REQUIRE_THROWS_AS(C{int_min} / S{-1}, std::overflow_error); REQUIRE_THROWS_AS(S{int_min} / C{-1}, std::overflow_error); } SECTION("modulo by zero throws") { REQUIRE_THROWS_AS(C{10} % S{0}, std::overflow_error); REQUIRE_THROWS_AS(S{10} % C{0}, std::overflow_error); } }