Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions cpp/src/arrow/array/array_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3445,16 +3445,16 @@ TEST_P(Decimal256Test, WithNulls) {
std::vector<Decimal256> draw = {Decimal256(1), Decimal256(2), Decimal256(-1),
Decimal256(4), Decimal256(-1), Decimal256(1),
Decimal256(2)};
Decimal256 big; // (pow(2, 255) - 1) / pow(10, 38)
Decimal256 big; // trimmed to kMaxPrecision (76) digits
ASSERT_OK_AND_ASSIGN(big,
Decimal256::FromString("578960446186580977117854925043439539266."
"34992332820282019728792003956564819967"));
"3499233282028201972879200395656481996"));
draw.push_back(big);

Decimal256 big_negative; // -pow(2, 255) / pow(10, 38)
Decimal256 big_negative; // trimmed to kMaxPrecision (76) digits
ASSERT_OK_AND_ASSIGN(big_negative,
Decimal256::FromString("-578960446186580977117854925043439539266."
"34992332820282019728792003956564819968"));
"3499233282028201972879200395656481996"));
draw.push_back(big_negative);

std::vector<uint8_t> valid_bytes = {true, true, false, true, false,
Expand Down
5 changes: 3 additions & 2 deletions cpp/src/arrow/json/converter_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -254,9 +254,10 @@ TEST(ConverterTest, Decimal128And256PrecisionError) {
std::shared_ptr<StructArray> parse_array;
ASSERT_OK(ParseFromString(options, json_source, &parse_array));

// FromString now rejects precision overflow before the JSON converter wraps
// the error, so match on the shared body only.
std::string error_msg =
"Invalid: Failed to convert JSON to " + decimal_type->ToString() +
": 123456789012345678901234567890.0123456789 requires precision 40";
"123456789012345678901234567890.0123456789 requires precision 40";
EXPECT_RAISES_WITH_MESSAGE_THAT(
Invalid, ::testing::HasSubstr(error_msg),
Convert(decimal_type, parse_array->GetFieldByName("")));
Expand Down
14 changes: 14 additions & 0 deletions cpp/src/arrow/util/decimal.cc
Original file line number Diff line number Diff line change
Expand Up @@ -879,6 +879,13 @@ Status DecimalFromString(const char* type_name, std::string_view s, Decimal* out
}
int32_t parsed_precision = static_cast<int32_t>(significant_digits);

// Reject precisions that exceed the target decimal's max (GH-49817).
if (parsed_precision > Decimal::kMaxPrecision) {
return Status::Invalid(s, " requires precision ", parsed_precision,
" which exceeds the ", type_name, " maximum of ",
Decimal::kMaxPrecision);
}

int32_t parsed_scale = 0;
if (dec.has_exponent) {
auto adjusted_exponent = dec.exponent;
Expand Down Expand Up @@ -943,6 +950,13 @@ Status SimpleDecimalFromString(const char* type_name, std::string_view s,
}
int32_t parsed_precision = static_cast<int32_t>(significant_digits);

// Reject precisions that exceed the target decimal's max (GH-49817).
if (parsed_precision > DecimalClass::kMaxPrecision) {
return Status::Invalid(s, " requires precision ", parsed_precision,
" which exceeds the ", type_name, " maximum of ",
DecimalClass::kMaxPrecision);
}

int32_t parsed_scale = 0;
if (dec.has_exponent) {
auto adjusted_exponent = dec.exponent;
Expand Down
66 changes: 45 additions & 21 deletions cpp/src/arrow/util/decimal_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,10 @@ TEST(Decimal128Test, TestStringRoundTrip) {
Decimal128 decimal(high_bits, low_bits);
for (int32_t scale : kScales) {
std::string str = decimal.ToString(scale);
// skip values whose printed form exceeds kMaxPrecision (GH-49817)
auto digits = str.size() - (str[0] == '-' ? 1 : 0) -
(str.find('.') == std::string::npos ? 0 : 1);
if (static_cast<int>(digits) > Decimal128::kMaxPrecision) continue;
ASSERT_OK_AND_ASSIGN(Decimal128 result, Decimal128::FromString(str));
EXPECT_EQ(decimal, result);
}
Expand Down Expand Up @@ -708,17 +712,23 @@ TEST(Decimal128Test, PrintLargeNegativeValue) {
}

TEST(Decimal128Test, PrintMaxValue) {
const std::string string_value("170141183460469231731687303715884105727");
const Decimal128 value(string_value);
const std::string printed_value = value.ToIntegerString();
ASSERT_EQ(string_value, printed_value);
// 99999...9 is the largest value that fits in kMaxPrecision (38) digits; the
// truncated bit-pattern max is also exercised (GH-49817).
for (const auto& string_value :
{std::string("99999999999999999999999999999999999999"),
std::string("17014118346046923173168730371588410572")}) {
const Decimal128 value(string_value);
ASSERT_EQ(string_value, value.ToIntegerString());
}
}

TEST(Decimal128Test, PrintMinValue) {
const std::string string_value("-170141183460469231731687303715884105728");
const Decimal128 value(string_value);
const std::string printed_value = value.ToIntegerString();
ASSERT_EQ(string_value, printed_value);
for (const auto& string_value :
{std::string("-99999999999999999999999999999999999999"),
std::string("-17014118346046923173168730371588410572")}) {
const Decimal128 value(string_value);
ASSERT_EQ(string_value, value.ToIntegerString());
}
}

struct ToStringTestParam {
Expand Down Expand Up @@ -1740,9 +1750,11 @@ TYPED_TEST(TestBasicDecimalFunctionality, FitsInPrecision) {
ASSERT_TRUE(TypeParam(max_nines).FitsInPrecision(TypeParam::kMaxPrecision));
ASSERT_TRUE(TypeParam("-" + max_nines).FitsInPrecision(TypeParam::kMaxPrecision));

std::string max_zeros(TypeParam::kMaxPrecision, '0');
ASSERT_FALSE(TypeParam("1" + max_zeros).FitsInPrecision(TypeParam::kMaxPrecision));
ASSERT_FALSE(TypeParam("-1" + max_zeros).FitsInPrecision(TypeParam::kMaxPrecision));
// max_nines + 1 is 10^kMaxPrecision, the first value that no longer fits;
// build it arithmetically since the string form is rejected now (GH-49817)
TypeParam one_past_max = TypeParam(max_nines) + 1;
ASSERT_FALSE(one_past_max.FitsInPrecision(TypeParam::kMaxPrecision));
ASSERT_FALSE((-one_past_max).FitsInPrecision(TypeParam::kMaxPrecision));
}

TEST(Decimal32Test, LeftShift) {
Expand All @@ -1763,9 +1775,11 @@ TEST(Decimal32Test, LeftShift) {
check(123, 16);
check(123, 30);

ASSERT_EQ(Decimal32("1999999998"), Decimal32("999999999") << 1);
// operands picked so the expected results fit in kMaxPrecision (9) digits,
// since FromString now rejects wider literals (GH-49817)
ASSERT_EQ(Decimal32("999999998"), Decimal32("499999999") << 1);
ASSERT_EQ(Decimal32("12799872"), Decimal32("99999") << 7);
ASSERT_EQ(Decimal32("1638383616"), Decimal32("99999") << 14);
ASSERT_EQ(Decimal32("819191808"), Decimal32("99999") << 13);

ASSERT_EQ(Decimal32("123456789"), Decimal32("123456789") << 0);
ASSERT_EQ(Decimal32("246913578"), Decimal32("123456789") << 1);
Expand All @@ -1777,9 +1791,11 @@ TEST(Decimal32Test, LeftShift) {
check(-123, 16);
check(-123, 30);

ASSERT_EQ(Decimal32("-1999999998"), Decimal32("-999999999") << 1);
// operands picked so the expected results fit in kMaxPrecision (9) digits,
// since FromString now rejects wider literals (GH-49817)
ASSERT_EQ(Decimal32("-999999998"), Decimal32("-499999999") << 1);
ASSERT_EQ(Decimal32("-12799872"), Decimal32("-99999") << 7);
ASSERT_EQ(Decimal32("-1638383616"), Decimal32("-99999") << 14);
ASSERT_EQ(Decimal32("-819191808"), Decimal32("-99999") << 13);

ASSERT_EQ(Decimal32("-123456789"), Decimal32("-123456789") << 0);
ASSERT_EQ(Decimal32("-246913578"), Decimal32("-123456789") << 1);
Expand Down Expand Up @@ -1852,7 +1868,9 @@ TEST(Decimal64Test, LeftShift) {

ASSERT_EQ(Decimal64("1234567890123456"), Decimal64("1234567890123456") << 0);
ASSERT_EQ(Decimal64("2469135780246912"), Decimal64("1234567890123456") << 1);
ASSERT_EQ(Decimal64("6917529027641081856"), Decimal64("1234567890123456") << 55);
// shift amount picked so the expected result fits in kMaxPrecision (18
// digits), since FromString now rejects wider literals (GH-49817)
ASSERT_EQ(Decimal64("632098759743209472"), Decimal64("1234567890123456") << 9);

check(-123, 0);
check(-123, 1);
Expand All @@ -1866,7 +1884,9 @@ TEST(Decimal64Test, LeftShift) {

ASSERT_EQ(Decimal64("-1234567890123456"), Decimal64("-1234567890123456") << 0);
ASSERT_EQ(Decimal64("-2469135780246912"), Decimal64("-1234567890123456") << 1);
ASSERT_EQ(Decimal64("-6917529027641081856"), Decimal64("-1234567890123456") << 55);
// shift amount picked so the expected result fits in kMaxPrecision (18
// digits), since FromString now rejects wider literals (GH-49817)
ASSERT_EQ(Decimal64("-632098759743209472"), Decimal64("-1234567890123456") << 9);
}

TEST(Decimal64Test, RightShift) {
Expand Down Expand Up @@ -1932,8 +1952,10 @@ TEST(Decimal128Test, LeftShift) {

ASSERT_EQ(Decimal128("199999999999998"), Decimal128("99999999999999") << 1);
ASSERT_EQ(Decimal128("3435973836799965640261632"), Decimal128("99999999999999") << 35);
ASSERT_EQ(Decimal128("120892581961461708544797985370825293824"),
Decimal128("99999999999999") << 80);
// shift amount picked so the expected result fits in kMaxPrecision (38
// digits), since FromString now rejects wider literals (GH-49817)
ASSERT_EQ(Decimal128("60446290980730854272398992685412646912"),
Decimal128("99999999999999") << 79);

ASSERT_EQ(Decimal128("1234567890123456789012"), Decimal128("1234567890123456789012")
<< 0);
Expand All @@ -1951,8 +1973,10 @@ TEST(Decimal128Test, LeftShift) {
ASSERT_EQ(Decimal128("-199999999999998"), Decimal128("-99999999999999") << 1);
ASSERT_EQ(Decimal128("-3435973836799965640261632"), Decimal128("-99999999999999")
<< 35);
ASSERT_EQ(Decimal128("-120892581961461708544797985370825293824"),
Decimal128("-99999999999999") << 80);
// shift amount picked so the expected result fits in kMaxPrecision (38
// digits), since FromString now rejects wider literals (GH-49817)
ASSERT_EQ(Decimal128("-60446290980730854272398992685412646912"),
Decimal128("-99999999999999") << 79);

ASSERT_EQ(Decimal128("-1234567890123456789012"), Decimal128("-1234567890123456789012")
<< 0);
Expand Down
Loading