diff --git a/dbms/src/Functions/divide.cpp b/dbms/src/Functions/divide.cpp
index b551f4d1f15..753afe9e109 100644
--- a/dbms/src/Functions/divide.cpp
+++ b/dbms/src/Functions/divide.cpp
@@ -46,9 +46,36 @@ struct TiDBDivideFloatingImpl
using ResultType = typename NumberTraits::ResultOfFloatingPointDivision::Type;
template
- static Result apply(A a, B b)
+ static Result apply(A x, B d)
{
- 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;
}
template
static Result apply(A a, B b, UInt8 & res_null)
@@ -61,7 +88,7 @@ struct TiDBDivideFloatingImpl
res_null = 1;
return static_cast(0);
}
- return static_cast(a) / b;
+ return apply(a, b);
}
};
@@ -88,7 +115,7 @@ struct TiDBDivideFloatingImpl
res_null = 1;
return static_cast(0);
}
- return static_cast(a) / static_cast(b);
+ return apply(a, b);
}
};
@@ -318,4 +345,4 @@ void registerFunctionDivideIntegralOrZero(FunctionFactory & factory)
factory.registerFunction();
}
-} // namespace DB
\ No newline at end of file
+} // namespace DB
diff --git a/dbms/src/Functions/tests/gtest_arithmetic_functions.cpp b/dbms/src/Functions/tests/gtest_arithmetic_functions.cpp
index 80aa231f237..1a2977771d9 100644
--- a/dbms/src/Functions/tests/gtest_arithmetic_functions.cpp
+++ b/dbms/src/Functions/tests/gtest_arithmetic_functions.cpp
@@ -5,7 +5,9 @@
#include
#include
#include
+#include
+#include
#include
#include
#include
@@ -89,6 +91,142 @@ class TestBinaryArithmeticFunctions : public DB::tests::FunctionTest
}
};
+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(19, 4), {DecimalField128(1, 4), DecimalField128(1, 4), DecimalField128(1, 4), DecimalField128(1, 4), DecimalField128(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(19, 4), {DecimalField128(1, 4), DecimalField128(1, 4), DecimalField128(1, 4), DecimalField128(1, 4), DecimalField128(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(19, 4), {DecimalField128(1, 4), DecimalField128(1, 4), DecimalField128(1, 4), DecimalField128(1, 4), DecimalField128(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(19, 4), {DecimalField128(1, 4), DecimalField128(1, 4), DecimalField128(1, 4), DecimalField128(1, 4), DecimalField128(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
+
TEST_F(TestBinaryArithmeticFunctions, TiDBDivideDecimal)
try
{
diff --git a/tests/tidb-ci/fullstack-test-dt/issue_1425.test b/tests/tidb-ci/fullstack-test-dt/issue_1425.test
index 02ddfcfafd5..cd89677400e 100644
--- a/tests/tidb-ci/fullstack-test-dt/issue_1425.test
+++ b/tests/tidb-ci/fullstack-test-dt/issue_1425.test
@@ -2,14 +2,21 @@ mysql> drop table if exists test.t;
mysql> create table test.t (id int, value decimal(7,4), c1 int, c2 int);
-mysql> insert into test.t values(1,1.9286,54,28);
+mysql> insert into test.t values (1,1.9285,54,28), (1,1.9286,54,28);
mysql> alter table test.t set tiflash replica 1;
func> wait_table test t
+# note: ref to https://github.com/pingcap/tiflash/issues/1682,
+# The precision of tiflash results is different from that of tidb, which is a compatibility issue
mysql> use test; set session tidb_isolation_read_engines='tiflash'; select * from t where value = 54/28;
mysql> use test; set session tidb_isolation_read_engines='tiflash'; select * from t where value = c1/c2;
++------+--------+------+------+
+| id | value | c1 | c2 |
++------+--------+------+------+
+| 1 | 1.9286 | 54 | 28 |
++------+--------+------+------+
mysql> drop table if exists test.t;