This is an automated email from the ASF dual-hosted git repository.
Mryange pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new d3f92ed7be0 [fix](be) Fix BIGINT conv overflow during base conversion
(#67872)
d3f92ed7be0 is described below
commit d3f92ed7be0ef7e42131d4af365ab8626e9039a4
Author: Mryange <[email protected]>
AuthorDate: Tue Sep 22 15:43:06 2026 +0800
[fix](be) Fix BIGINT conv overflow during base conversion (#67872)
`conv()` returned incorrect results for BIGINT inputs when converting
from non-decimal bases because the integer path used 32-bit intermediate
place values. Use unsigned 64-bit accumulation with pre-operation
overflow checks and preserve existing invalid-digit and signed-value
handling.
### Release note
None
### Check List (For Author)
- Test <!-- At least one of them must be included. -->
- [ ] Regression test
- [ ] Unit Test
- [ ] Manual test (add detailed scripts or steps below)
- [ ] No need to test or manual test. Explain why:
- [ ] This is a refactor/code format and no logic has been changed.
- [ ] Previous test can cover this change.
- [ ] No code files have been changed.
- [ ] Other reason <!-- Add your reason? -->
- Behavior changed:
- [ ] No.
- [ ] Yes. <!-- Explain the behavior change -->
- Does this need documentation?
- [ ] No.
- [ ] Yes. <!-- Add document PR link here. eg:
https://github.com/apache/doris-website/pull/1214 -->
### Check List (For Reviewer who merge this PR)
- [ ] Confirm the release note
- [ ] Confirm test cases
- [ ] Confirm document
- [ ] Add branch pick label <!-- Add branch pick label that this PR
should merge into -->
---
be/src/exprs/math_functions.cpp | 55 +++++++++++++++------------
be/test/exprs/function/function_math_test.cpp | 23 +++++++++++
be/test/exprs/math_functions_test.cpp | 40 +++++++++++++++++++
3 files changed, 93 insertions(+), 25 deletions(-)
diff --git a/be/src/exprs/math_functions.cpp b/be/src/exprs/math_functions.cpp
index af727d8a5ac..b9db9476bf2 100644
--- a/be/src/exprs/math_functions.cpp
+++ b/be/src/exprs/math_functions.cpp
@@ -104,14 +104,10 @@ StringRef MathFunctions::decimal_to_base(FunctionContext*
ctx, int64_t src_num,
char buf[max_digits];
int32_t result_len = 0;
int32_t buf_index = max_digits - 1;
- uint64_t temp_num;
- if (dest_base < 0) {
- // Dest base is negative, treat src_num as signed.
- temp_num = std::abs(src_num);
- } else {
- // Dest base is positive. We must interpret src_num in 2's complement.
- // Convert to an unsigned int to properly deal with 2's complement
conversion.
- temp_num = static_cast<uint64_t>(src_num);
+ auto temp_num = static_cast<uint64_t>(src_num);
+ if (dest_base < 0 && src_num < 0) {
+ // Compute the signed magnitude without overflowing for INT64_MIN.
+ temp_num = uint64_t {0} - temp_num;
}
int abs_base = std::abs(dest_base);
do {
@@ -133,26 +129,35 @@ StringRef MathFunctions::decimal_to_base(FunctionContext*
ctx, int64_t src_num,
}
bool MathFunctions::decimal_in_base_to_decimal(int64_t src_num, int8_t
src_base, int64_t* result) {
- uint64_t temp_num = std::abs(src_num);
- int32_t place = 1;
- *result = 0;
+ auto magnitude = static_cast<uint64_t>(src_num);
+ if (src_num < 0) {
+ magnitude = 0 - magnitude;
+ }
+ uint64_t divisor = 1;
+ uint64_t remaining = magnitude;
+ while (remaining >= 10) {
+ remaining /= 10;
+ divisor *= 10;
+ }
+
+ uint64_t value = 0;
+ constexpr uint64_t max_value = std::numeric_limits<uint64_t>::max();
+ const uint64_t max_div_base = max_value / src_base;
+ const uint64_t max_mod_base = max_value % src_base;
do {
- int32_t digit = temp_num % 10;
- // Reset result if digit is not representable in src_base.
+ const int digit = static_cast<int>(magnitude / divisor);
+ // Keep the prefix preceding the first digit not representable in
src_base.
if (digit >= src_base) {
- *result = 0;
- place = 1;
- } else {
- *result += digit * place;
- place *= src_base;
- // Overflow.
- if (UNLIKELY(*result < digit)) {
- return false;
- }
+ break;
}
- temp_num /= 10;
- } while (temp_num > 0);
- *result = (src_num < 0) ? -(*result) : *result;
+ if (UNLIKELY(value > max_div_base - (digit > max_mod_base))) {
+ return false;
+ }
+ value = value * src_base + digit;
+ magnitude %= divisor;
+ divisor /= 10;
+ } while (divisor > 0);
+ *result = static_cast<int64_t>(src_num < 0 ? 0 - value : value);
return true;
}
diff --git a/be/test/exprs/function/function_math_test.cpp
b/be/test/exprs/function/function_math_test.cpp
index cf1b3a442ea..318ca61dc72 100644
--- a/be/test/exprs/function/function_math_test.cpp
+++ b/be/test/exprs/function/function_math_test.cpp
@@ -636,6 +636,29 @@ TEST(MathFunctionTest, conv_test) {
}
}
+TEST(MathFunctionTest, conv_int64_boundary_test) {
+ InputTypeSet input_types = {PrimitiveType::TYPE_BIGINT,
PrimitiveType::TYPE_TINYINT,
+ PrimitiveType::TYPE_TINYINT};
+ DataSet data_set = {
+ {{BIGINT(8000000000000000LL), TINYINT(16), TINYINT(-10)},
+ VARCHAR("-9223372036854775808")},
+ {{BIGINT(8000000000000000LL), TINYINT(16), TINYINT(10)},
+ VARCHAR("9223372036854775808")},
+ {{BIGINT(std::numeric_limits<int64_t>::min()), TINYINT(10),
TINYINT(-10)},
+ VARCHAR("-9223372036854775808")},
+ {{BIGINT(8000000000000000LL), TINYINT(16), TINYINT(-16)},
VARCHAR("-8000000000000000")},
+ {{BIGINT(10000000000000000LL), TINYINT(16), TINYINT(-10)},
VARCHAR("-1")},
+ {{BIGINT(10000000000000000LL), TINYINT(16), TINYINT(10)},
+ VARCHAR("18446744073709551615")},
+ {{BIGINT(-255), TINYINT(10), TINYINT(-16)}, VARCHAR("-FF")},
+ {{BIGINT(-1), TINYINT(10), TINYINT(16)},
VARCHAR("FFFFFFFFFFFFFFFF")},
+ {{BIGINT(255), TINYINT(10), TINYINT(-16)}, VARCHAR("FF")},
+ {{BIGINT(0), TINYINT(16), TINYINT(-10)}, VARCHAR("0")},
+ {{Null(), TINYINT(16), TINYINT(-10)}, Null()},
+ };
+ check_function_all_arg_comb<DataTypeString, true>("conv", input_types,
data_set);
+}
+
TEST(MathFunctionTest, money_format_test) {
std::string func_name = "money_format";
diff --git a/be/test/exprs/math_functions_test.cpp
b/be/test/exprs/math_functions_test.cpp
index ce0fc635d64..d2d6a9d7c08 100644
--- a/be/test/exprs/math_functions_test.cpp
+++ b/be/test/exprs/math_functions_test.cpp
@@ -25,6 +25,46 @@ namespace doris {
struct MathFunctionsTest : public ::testing::Test {};
+TEST_F(MathFunctionsTest, DecimalInBaseToDecimal) {
+ struct TestCase {
+ int64_t input;
+ int8_t base;
+ uint64_t expected;
+ TestCase(int64_t input_value, int8_t base_value, uint64_t
expected_value)
+ : input(input_value), base(base_value),
expected(expected_value) {}
+ };
+ const TestCase cases[] = {
+ {0, 16, 0},
+ {1111111111, 16, 73300775185ULL},
+ {10000000, 16, 268435456},
+ {80000000, 16, 2147483648ULL},
+ {100000000, 16, 4294967296ULL},
+ {8000000000000000, 16, 1ULL << 63},
+ {std::numeric_limits<int64_t>::min(), 10, 1ULL << 63},
+ {-1111111111, 16, 0ULL - 73300775185ULL},
+ {15, 4, 1},
+ {12345, 4, 27},
+ {5111, 4, 0},
+ // Invalid leading digits produce an empty prefix.
+ {999999999999999999, 9, 0},
+ {std::numeric_limits<int64_t>::max(), 10,
+ static_cast<uint64_t>(std::numeric_limits<int64_t>::max())},
+ };
+ for (const auto& test : cases) {
+ SCOPED_TRACE(test.input);
+ int64_t result = 0;
+ ASSERT_TRUE(MathFunctions::decimal_in_base_to_decimal(test.input,
test.base, &result));
+ EXPECT_EQ(test.expected, static_cast<uint64_t>(result));
+ }
+}
+
+TEST_F(MathFunctionsTest, DecimalInBaseToDecimalOverflow) {
+ int64_t result = 0;
+ EXPECT_FALSE(MathFunctions::decimal_in_base_to_decimal(10000000000000000,
16, &result));
+ EXPECT_FALSE(MathFunctions::decimal_in_base_to_decimal(-10000000000000000,
16, &result));
+ EXPECT_FALSE(MathFunctions::decimal_in_base_to_decimal(999999999999999999,
36, &result));
+}
+
// Regular rounding test (truncate = false)
TEST_F(MathFunctionsTest, DoubleRoundBasic) {
// Positive number rounding
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]