diff --git a/modules/core/include/opencv2/core/types.hpp b/modules/core/include/opencv2/core/types.hpp index dab6a82dcc..1f54d9563d 100644 --- a/modules/core/include/opencv2/core/types.hpp +++ b/modules/core/include/opencv2/core/types.hpp @@ -1245,6 +1245,26 @@ _Tp Point_<_Tp>::dot(const Point_& pt) const return saturate_cast<_Tp>(x*pt.x + y*pt.y); } +template<> inline +int Point_::dot(const Point_& pt) const +{ + const int64_t xx = (int64_t)x * pt.x; + const int64_t yy = (int64_t)y * pt.y; + + // detect int64 overflow before adding + if (yy > 0 && xx > INT64_MAX - yy) + { + return INT32_MAX; + } + if (yy < 0 && xx < INT64_MIN - yy) + { + return INT32_MIN; + } + + const int64_t v = xx + yy; + return cv::saturate_cast(v); +} + template inline double Point_<_Tp>::ddot(const Point_& pt) const { @@ -1490,6 +1510,50 @@ _Tp Point3_<_Tp>::dot(const Point3_& pt) const return saturate_cast<_Tp>(x*pt.x + y*pt.y + z*pt.z); } +template<> inline +int Point3_::dot(const Point3_& pt) const +{ + const int64_t xx = (int64_t)x * pt.x; + const int64_t yy = (int64_t)y * pt.y; + const int64_t zz = (int64_t)z * pt.z; + + // Sort the three products so that lo <= mid <= hi. + // We add in the order (lo + hi) first, then add mid. + // This minimizes the absolute value of the intermediate sum, + // reducing the chance of int64 overflow during addition. + int64_t lo = xx, mid = yy, hi = zz; + if (lo > mid) std::swap(lo, mid); + if (mid > hi) std::swap(mid, hi); + if (lo > mid) std::swap(lo, mid); + + // Step 1: lo + hi + // If lo + hi overflows int64, the final result must saturate to INT32_MAX/INT32_MIN. + // The middle value (mid) cannot bring the result back into the int32 range, + // so we can return immediately without considering mid. + if (hi > 0 && lo > INT64_MAX - hi) + { + return INT32_MAX; + } + if (hi < 0 && lo < INT64_MIN - hi) + { + return INT32_MIN; + } + int64_t sum = lo + hi; + + // Step 2: sum + mid + if (mid > 0 && sum > INT64_MAX - mid) + { + return INT32_MAX; + } + if (mid < 0 && sum < INT64_MIN - mid) + { + return INT32_MIN; + } + sum += mid; + + return cv::saturate_cast(sum); +} + template inline double Point3_<_Tp>::ddot(const Point3_& pt) const { diff --git a/modules/core/test/test_math.cpp b/modules/core/test/test_math.cpp index 5c3df87ddb..1c9aa81d32 100644 --- a/modules/core/test/test_math.cpp +++ b/modules/core/test/test_math.cpp @@ -4229,5 +4229,177 @@ TEST(Core_FastMath, InlineIsInf) EXPECT_EQ( cvIsInf(suf.f), 0); } +//////////////////////////////////////////////////////////////////////////// +// See https://github.com/opencv/opencv/issues/28930 +typedef testing::TestWithParam> Core_Point_DotProduct_regression28930; + +TEST_P(Core_Point_DotProduct_regression28930, basic) +{ + const int x1 = std::get<0>(GetParam()); + const int y1 = std::get<1>(GetParam()); + const int x2 = std::get<2>(GetParam()); + const int y2 = std::get<3>(GetParam()); + const int expect = std::get<4>(GetParam()); + + cv::Point pt1(x1, y1); + cv::Point pt2(x2, y2); + + EXPECT_EQ(pt1.dot(pt2), expect) << "Failed for: (" << x1 << "," << y1 << ") dot (" << x2 << "," << y2 << ")"; +} + +INSTANTIATE_TEST_CASE_P(/* */, Core_Point_DotProduct_regression28930, +testing::Values( + // 1. INT_MIN*INT_MIN + INT_MIN*INT_MIN = 2^62 + 2^62 => Saturates to MAX + std::make_tuple( INT_MIN, INT_MIN, + INT_MIN, INT_MIN, + INT_MAX), + + // 2. INT_MIN*INT_MAX + INT_MAX*INT_MAX = 4611686014132420609 + -4611686016279904256 = -2147483647 + std::make_tuple( INT_MAX, INT_MIN, + INT_MAX, INT_MAX, + -2147483647), + + // 3. INT_MAX*INT_MAX + INT_MAX*INT_MIN = 4611686014132420609 + -4611686016279904256 = -2147483647 + std::make_tuple( INT_MAX, INT_MAX, + INT_MAX, INT_MIN, + -2147483647), + + // 4. 46340^2 = 2147395600 (Under INT_MAX) + std::make_tuple( 46340, 0, + 46340, 0, + 2147395600), + + // 5. 46341^2 = 2147488281 (Over INT_MAX) => Saturates to MAX + std::make_tuple( 46341, 0, + 46341, 0, + INT_MAX), + + // 6. -46340 * 46340 = -2147395600 (Over INT_MIN) + std::make_tuple( -46340, 0, + 46340, 0, + -2147395600), + + // 7. -46341 * 46341 = -2147488281 (Under INT_MIN) => Saturates to MIN + std::make_tuple( -46341, 0, + 46341, 0, + INT_MIN), + + // 8. Zero + std::make_tuple( 0, 0, + 0, 0, + 0), + + // 9. Simple max + std::make_tuple( INT_MAX, 0, + 1, 0, + INT_MAX) +)); + +typedef testing::TestWithParam> Core_Point3i_DotProduct_regression28930; + +TEST_P(Core_Point3i_DotProduct_regression28930, basic) +{ + const int x1 = std::get<0>(GetParam()); + const int y1 = std::get<1>(GetParam()); + const int z1 = std::get<2>(GetParam()); + const int x2 = std::get<3>(GetParam()); + const int y2 = std::get<4>(GetParam()); + const int z2 = std::get<5>(GetParam()); + const int expect = std::get<6>(GetParam()); + + cv::Point3i pt1(x1, y1, z1); + cv::Point3i pt2(x2, y2, z2); + + EXPECT_EQ(pt1.dot(pt2), expect) << "Failed for: (" << x1 << "," << y1 << "," << z1 << ") dot (" << x2 << "," << y2 << "," << z2 << ")"; +} + +INSTANTIATE_TEST_CASE_P(/* */, Core_Point3i_DotProduct_regression28930, +testing::Values( + // 1. INT_MIN*INT_MIN + INT_MIN*INT_MIN = 2^62 + 2^62 => Saturates to MAX + std::make_tuple( INT_MIN, INT_MIN, 0, + INT_MIN, INT_MIN, 0, + INT_MAX), + + // 2. INT_MIN*INT_MAX + INT_MAX*INT_MAX = 4611686014132420609 + -4611686016279904256 = -2147483647 + std::make_tuple( INT_MAX, INT_MIN, 0, + INT_MAX, INT_MAX, 0, + -2147483647), + + // 3. INT_MAX*INT_MAX + INT_MAX*INT_MIN = 4611686014132420609 + -4611686016279904256 = -2147483647 + std::make_tuple( INT_MAX, INT_MAX, 0, + INT_MAX, INT_MIN, 0, + -2147483647), + + // 4. 46340^2 = 2147395600 (Under INT_MAX) + std::make_tuple( 46340, 0, 0, + 46340, 0, 0, + 2147395600), + + // 5. 46341^2 = 2147488281 (Over INT_MAX) => Saturates to MAX + std::make_tuple( 46341, 0, 0, + 46341, 0, 0, + INT_MAX), + + // 6. -46340 * 46340 = -2147395600 (Over INT_MIN) + std::make_tuple( -46340, 0, 0, + 46340, 0, 0, + -2147395600), + + // 7. -46341 * 46341 = -2147488281 (Under INT_MIN) => Saturates to MIN + std::make_tuple( -46341, 0, 0, + 46341, 0, 0, + INT_MIN), + + // 8. Zero + std::make_tuple( 0, 0, 0, + 0, 0, 0, + 0), + + // 9. Simple max + std::make_tuple( INT_MAX, 0, 0, + 1, 0, 0, + INT_MAX), + + // 10. All positive, no overflow + std::make_tuple( 1, 2, 3, + 4, 5, 6, + (1*4 + 2*5 + 3*6)), + + // 11. All negative, no overflow + std::make_tuple( -1, -2, -3, + -4, -5, -6, + (-1*-4 + -2*-5 + -3*-6)), + + // 12. Three large positive products => saturate to MAX + std::make_tuple( INT_MAX, INT_MAX, INT_MAX, + INT_MAX, INT_MAX, INT_MAX, + INT_MAX), + + // 13. Three large positive products from INT_MIN*INT_MIN => saturate to MAX + std::make_tuple( INT_MIN, INT_MIN, INT_MIN, + INT_MIN, INT_MIN, INT_MIN, + INT_MAX), + + // 14. Three large negative products => saturate to MIN + std::make_tuple( INT_MIN, INT_MIN, INT_MIN, + INT_MAX, INT_MAX, INT_MAX, + INT_MIN), + + // 15. Mixed signs: + + - + std::make_tuple( INT_MAX, INT_MAX, INT_MIN, + INT_MAX, INT_MAX, INT_MAX, + INT_MAX), + + // 16. Mixed signs: + - + + std::make_tuple( INT_MAX, INT_MIN, INT_MAX, + INT_MAX, INT_MAX, INT_MAX, + INT_MAX), + + // 17. Mixed signs: - + - + std::make_tuple( INT_MIN, INT_MAX, INT_MIN, + INT_MAX, INT_MAX, INT_MAX, + INT_MIN) +)); + }} // namespace /* End of file. */