diff --git a/source/blender/blenlib/BLI_math_base.hh b/source/blender/blenlib/BLI_math_base.hh index bd47ed94eb9..f0878ef9672 100644 --- a/source/blender/blenlib/BLI_math_base.hh +++ b/source/blender/blenlib/BLI_math_base.hh @@ -103,19 +103,19 @@ template inline T round(const T &a) /** * Repeats the saw-tooth pattern even on negative numbers. - * ex: `mod_periodic(-3, 4) = 1`, `mod(-3, 4)= -3` + * ex: `mod_periodic(-3, 4) = 1`, `mod(-3, 4)= -3`. This will cause undefined behavior for negative + * b. */ template inline T mod_periodic(const T &a, const T &b) { + BLI_assert(b > 0); + if constexpr (std::is_integral_v) { + BLI_assert(std::numeric_limits::max() - math::abs(a) >= b); + return ((a % b) + b) % b; + } + return a - (b * math::floor(a / b)); } -template<> inline int64_t mod_periodic(const int64_t &a, const int64_t &b) -{ - int64_t c = (a >= 0) ? a : (-1 - a); - int64_t tmp = c - (b * (c / b)); - /* Negative integers have different rounding that do not match floor(). */ - return (a >= 0) ? tmp : (b - 1 - tmp); -} template inline T ceil(const T &a) { diff --git a/source/blender/blenlib/tests/BLI_math_base_test.cc b/source/blender/blenlib/tests/BLI_math_base_test.cc index bdbfcfb3852..212e7288260 100644 --- a/source/blender/blenlib/tests/BLI_math_base_test.cc +++ b/source/blender/blenlib/tests/BLI_math_base_test.cc @@ -211,4 +211,40 @@ TEST(math_base, FlooredFMod) EXPECT_FLOAT_EQ(floored_fmod(12345.0f, 12345.0f), 0.0f); } +TEST(math_base, ModPeriodic) +{ + EXPECT_FLOAT_EQ(math::mod_periodic(3.27f, 1.57f), 0.12999988f); + EXPECT_FLOAT_EQ(math::mod_periodic(327.f, 47.f), 45.f); + EXPECT_FLOAT_EQ(math::mod_periodic(-0.1f, 1.0f), 0.9f); + EXPECT_FLOAT_EQ(math::mod_periodic(-0.9f, 1.0f), 0.1f); + EXPECT_FLOAT_EQ(math::mod_periodic(-100.1f, 1.0f), 0.90000153f); + EXPECT_FLOAT_EQ(math::mod_periodic(-0.1f, 12345.0f), 12344.9f); + EXPECT_FLOAT_EQ(math::mod_periodic(12345.1f, 12345.0f), 0.099609375f); + EXPECT_FLOAT_EQ(math::mod_periodic(12344.999f, 12345.0f), 12344.999f); + EXPECT_FLOAT_EQ(math::mod_periodic(12345.0f, 12345.0f), 0.0f); + + EXPECT_EQ(math::mod_periodic(1, 10), 1); + EXPECT_EQ(math::mod_periodic(11, 10), 1); + EXPECT_EQ(math::mod_periodic(-1, 10), 9); + EXPECT_EQ(math::mod_periodic(-11, 10), 9); + EXPECT_EQ(math::mod_periodic(1, 1), 0); + EXPECT_EQ(math::mod_periodic(0, 99999), 0); + EXPECT_EQ(math::mod_periodic(99999, 99999), 0); + + EXPECT_EQ( + math::mod_periodic(std::numeric_limits::max() / 2, std::numeric_limits::max() / 2), + 0); + EXPECT_EQ( + math::mod_periodic(std::numeric_limits::min() / 2, std::numeric_limits::max() / 2), + std::numeric_limits::max() / 2 - 1); + + EXPECT_EQ(math::mod_periodic(1, 10), 1); + EXPECT_EQ(math::mod_periodic(11, 10), 1); + EXPECT_EQ(math::mod_periodic(-1, 10), 9); + EXPECT_EQ(math::mod_periodic(-11, 10), 9); + EXPECT_EQ(math::mod_periodic(1, 1), 0); + EXPECT_EQ(math::mod_periodic(0, 99999), 0); + EXPECT_EQ(math::mod_periodic(99999, 99999), 0); +} + } // namespace blender::tests