diff --git a/dbms/src/Functions/divide.cpp b/dbms/src/Functions/divide.cpp index 53c5de79448..8ca5fe081f5 100644 --- a/dbms/src/Functions/divide.cpp +++ b/dbms/src/Functions/divide.cpp @@ -62,7 +62,38 @@ struct TiDBDivideFloatingImpl template static Result apply(A a, B b) { +<<<<<<< HEAD return static_cast(a) / b; +======= + /// ref https://github.com/pingcap/tiflash/issues/6462 + /// For division of Decimal/Decimal or Int/Decimal or Decimal/Int, we should round the result to make compatible with TiDB. + /// basically refer to https://stackoverflow.com/a/71634489 + if constexpr (std::is_integral_v || std::is_same_v || std::is_same_v) + { + /// 1. do division first, get the quotient and mod, todo:(perf) find a unified `divmod` function to speed up this. + Result quotient = x / d; + Result mod = x % d; + /// 2. get the half of divisor, which is threshold to decide whether to round up or down. + /// note: don't directly use bit operation here, it may cause unexpected result. + Result half = (d / 2) + (d % 2); + + /// 3. compare the abstract values of mod and half, if mod >= half, then round up. + Result abs_m = mod < 0 ? -mod : mod; + Result abs_h = half < 0 ? -half : half; + if (abs_m >= abs_h) + { + /// 4. now we need to round up, i.e., add 1 to the quotient's absolute value. + /// if the signs of dividend and divisor are the same, then the quotient should be positive, otherwise negative. + if ((x < 0) == (d < 0)) // same_sign, i.e., quotient >= 0 + quotient = quotient + 1; + else + quotient = quotient - 1; + } + return quotient; + } + else + return static_cast(x) / d; +>>>>>>> c6d55aff39 (fix(expr): round decimal divide result when return type is Int512 (#7035)) } template static Result apply(A a, B b, UInt8 & res_null) diff --git a/dbms/src/Functions/tests/gtest_arithmetic_functions.cpp b/dbms/src/Functions/tests/gtest_arithmetic_functions.cpp index 1a542c908ee..bd18a70e36b 100644 --- a/dbms/src/Functions/tests/gtest_arithmetic_functions.cpp +++ b/dbms/src/Functions/tests/gtest_arithmetic_functions.cpp @@ -103,6 +103,145 @@ class TestBinaryArithmeticFunctions : public DB::tests::FunctionTest } }; +<<<<<<< HEAD +======= +template +void doTiDBDivideDecimalRoundInternalTest() +{ + auto apply = static_cast(&TiDBDivideFloatingImpl::apply); + + constexpr TYPE max = std::numeric_limits::max(); + // note: Int256's min is not equal to -max-1 + // according to https://www.boost.org/doc/libs/1_60_0/libs/multiprecision/doc/html/boost_multiprecision/tut/ints/cpp_int.html + constexpr TYPE min = std::numeric_limits::min(); + + // clang-format off + const std::vector> cases = { + {1, 2, 1}, {1, -2, -1}, {-1, 2, -1}, {-1, -2, 1}, + + {0, 3, 0}, {0, -3, 0}, {0, 3, 0}, {0, -3, 0}, + {1, 3, 0}, {1, -3, 0}, {-1, 3, 0}, {-1, -3, 0}, + {2, 3, 1}, {2, -3, -1}, {-2, 3, -1}, {-2, -3, 1}, + {3, 3, 1}, {3, -3, -1}, {-3, 3, -1}, {-3, -3, 1}, + {4, 3, 1}, {4, -3, -1}, {-4, 3, -1}, {-4, -3, 1}, + {5, 3, 2}, {5, -3, -2}, {-5, 3, -2}, {-5, -3, 2}, + + // ±max as divisor + {0, max, 0}, {max/2-1, max, 0}, {max/2, max, 0}, {max/2+1, max, 1}, {max-1, max, 1}, {max, max, 1}, + {-1, max, 0}, {-max/2+1, max, 0}, {-max/2, max, 0}, {-max/2-1, max, -1}, {-max+1, max, -1}, {-max, max, -1}, {min, max, -1}, + {0, -max, 0}, {max/2-1, -max, 0}, {max/2, -max, 0}, {max/2+1, -max, -1}, {max-1, -max, -1}, {max, -max, -1}, + {-1, -max, 0}, {-max/2+1, -max, 0}, {-max/2, -max, 0}, {-max/2-1, -max, 1}, {-max+1, -max, 1}, {-max, -max, 1}, {min, -max, 1}, + + // ±max as dividend + {max, 1, max}, {max, 2, max/2+1}, {max, max/2-1, 2}, {max, max/2, 2}, {max, max/2+1, 2}, {max, max-1, 1}, + {max, -1, -max}, {max, -2, -max/2-1}, {max, -max/2+1, -2}, {max, -max/2, -2}, {max, -max/2-1, -2}, {max, -max+1, -1}, + {-max, 1, -max}, {-max, 2, -max/2-1}, {-max, max/2+1, -2}, {-max, max/2, -2}, {-max, max/2-1, -2}, {-max, max-1, -1}, + {-max, -1, max}, {-max, -2, max/2+1}, {-max, -max/2-1, 2}, {-max, -max/2, 2}, {-max, -max/2+1, 2}, {-max, -max+1, 1}, + }; + // clang-format on + + for (const auto & expect : cases) + { + std::array actual = {expect[0], expect[1], apply(expect[0], expect[1])}; + ASSERT_EQ(expect, actual); + } +} + +TEST_F(TestBinaryArithmeticFunctions, TiDBDivideDecimalRoundInternal) +try +{ + doTiDBDivideDecimalRoundInternalTest(); + doTiDBDivideDecimalRoundInternalTest(); + doTiDBDivideDecimalRoundInternalTest(); + doTiDBDivideDecimalRoundInternalTest(); + doTiDBDivideDecimalRoundInternalTest(); +} +CATCH + +TEST_F(TestBinaryArithmeticFunctions, TiDBDivideDecimalRound) +try +{ + const String func_name = "tidbDivide"; + + // decimal32 + { + // int and decimal + ASSERT_COLUMN_EQ( + createColumn>(std::make_tuple(18, 4), {DecimalField64(1, 4), DecimalField64(1, 4), DecimalField64(1, 4), DecimalField64(1, 4), DecimalField64(0, 4)}), + executeFunction( + func_name, + createColumn({1, 1, 1, 1, 1}), + createColumn(std::make_tuple(20, 4), {DecimalField32(100000000, 4), DecimalField32(100010000, 4), DecimalField32(199990000, 4), DecimalField32(200000000, 4), DecimalField32(200010000, 4)}))); + + // decimal and decimal + ASSERT_COLUMN_EQ( + createColumn>(std::make_tuple(26, 8), {DecimalField128(10000, 8), DecimalField128(9999, 8), DecimalField128(5000, 8), DecimalField128(5000, 8), DecimalField128(5000, 8)}), + executeFunction( + func_name, + createColumn(std::make_tuple(18, 4), {DecimalField32(10000, 4), DecimalField32(10000, 4), DecimalField32(10000, 4), DecimalField32(10000, 4), DecimalField32(10000, 4)}), + createColumn(std::make_tuple(18, 4), {DecimalField32(100000000, 4), DecimalField32(100010000, 4), DecimalField32(199990000, 4), DecimalField32(200000000, 4), DecimalField32(200010000, 4)}))); + } + + // decimal64 + { + // int and decimal + ASSERT_COLUMN_EQ( + createColumn>(std::make_tuple(18, 4), {DecimalField64(1, 4), DecimalField64(1, 4), DecimalField64(1, 4), DecimalField64(1, 4), DecimalField64(0, 4)}), + executeFunction( + func_name, + createColumn({1, 1, 1, 1, 1}), + createColumn(std::make_tuple(20, 4), {DecimalField64(100000000, 4), DecimalField64(100010000, 4), DecimalField64(199990000, 4), DecimalField64(200000000, 4), DecimalField64(200010000, 4)}))); + + // decimal and decimal + ASSERT_COLUMN_EQ( + createColumn>(std::make_tuple(26, 8), {DecimalField128(10000, 8), DecimalField128(9999, 8), DecimalField128(5000, 8), DecimalField128(5000, 8), DecimalField128(5000, 8)}), + executeFunction( + func_name, + createColumn(std::make_tuple(18, 4), {DecimalField64(10000, 4), DecimalField64(10000, 4), DecimalField64(10000, 4), DecimalField64(10000, 4), DecimalField64(10000, 4)}), + createColumn(std::make_tuple(18, 4), {DecimalField64(100000000, 4), DecimalField64(100010000, 4), DecimalField64(199990000, 4), DecimalField64(200000000, 4), DecimalField64(200010000, 4)}))); + } + + // decimal128 + { + // int and decimal + ASSERT_COLUMN_EQ( + createColumn>(std::make_tuple(18, 4), {DecimalField64(1, 4), DecimalField64(1, 4), DecimalField64(1, 4), DecimalField64(1, 4), DecimalField64(0, 4)}), + executeFunction( + func_name, + createColumn({1, 1, 1, 1, 1}), + createColumn(std::make_tuple(20, 4), {DecimalField128(100000000, 4), DecimalField128(100010000, 4), DecimalField128(199990000, 4), DecimalField128(200000000, 4), DecimalField128(200010000, 4)}))); + + // decimal and decimal + ASSERT_COLUMN_EQ( + createColumn>(std::make_tuple(26, 8), {DecimalField128(10000, 8), DecimalField128(9999, 8), DecimalField128(5000, 8), DecimalField128(5000, 8), DecimalField128(5000, 8)}), + executeFunction( + func_name, + createColumn(std::make_tuple(18, 4), {DecimalField128(10000, 4), DecimalField128(10000, 4), DecimalField128(10000, 4), DecimalField128(10000, 4), DecimalField128(10000, 4)}), + createColumn(std::make_tuple(18, 4), {DecimalField128(100000000, 4), DecimalField128(100010000, 4), DecimalField128(199990000, 4), DecimalField128(200000000, 4), DecimalField128(200010000, 4)}))); + } + + // decimal256 + { + // int and decimal + ASSERT_COLUMN_EQ( + createColumn>(std::make_tuple(18, 4), {DecimalField64(1, 4), DecimalField64(1, 4), DecimalField64(1, 4), DecimalField64(1, 4), DecimalField64(0, 4)}), + executeFunction( + func_name, + createColumn({1, 1, 1, 1, 1}), + createColumn(std::make_tuple(20, 4), {DecimalField256(Int256(100000000), 4), DecimalField256(Int256(100010000), 4), DecimalField256(Int256(199990000), 4), DecimalField256(Int256(200000000), 4), DecimalField256(Int256(200010000), 4)}))); + + // decimal and decimal + ASSERT_COLUMN_EQ( + createColumn>(std::make_tuple(26, 8), {DecimalField128(10000, 8), DecimalField128(9999, 8), DecimalField128(5000, 8), DecimalField128(5000, 8), DecimalField128(5000, 8)}), + executeFunction( + func_name, + createColumn(std::make_tuple(18, 4), {DecimalField256(Int256(10000), 4), DecimalField256(Int256(10000), 4), DecimalField256(Int256(10000), 4), DecimalField256(Int256(10000), 4), DecimalField256(Int256(10000), 4)}), + createColumn(std::make_tuple(18, 4), {DecimalField256(Int256(100000000), 4), DecimalField256(Int256(100010000), 4), DecimalField256(Int256(199990000), 4), DecimalField256(Int256(200000000), 4), DecimalField256(Int256(200010000), 4)}))); + } +} +CATCH + +>>>>>>> c6d55aff39 (fix(expr): round decimal divide result when return type is Int512 (#7035)) TEST_F(TestBinaryArithmeticFunctions, TiDBDivideDecimal) try {