mirror of
https://github.com/scylladb/scylladb.git
synced 2026-05-12 19:02:12 +00:00
utils/big_decimal: fix scale overflow when parsing values with large exponents
The exponent of a big decimal string is parsed as an int32, adjusted for
the removed fractional part, and stored as an int32. When parsing values
like `1.23E-2147483647`, the unscaled value becomes `123`, and the scale
is adjusted to `2147483647 + 2 = 2147483649`. This exceeds the int32
limit, and since the scale is stored as an int32, it overflows and wraps
around, losing the value.
This patch fixes that the by parsing the exponent as an int64 value and
then adjusting it for the fractional part. The adjusted scale is then
checked to see if it is still within int32 limits before storing. An
exception is thrown if it is not within the int32 limits.
Note that strings with exponents that exceed the int32 range, like
`0.01E2147483650`, were previously not parseable as a big decimal. They
are now accepted if the final adjusted scale fits within int32 limits.
For the above value, unscaled_value = 1 and scale = -2147483648, so it
is now accepted. This is in line with how Java's `BigDecimal` parses
strings.
Fixes: #24581
Signed-off-by: Lakshmi Narayanan Sreethar <lakshmi.sreethar@scylladb.com>
Closes scylladb/scylladb#24640
(cherry picked from commit 279253ffd0)
Closes scylladb/scylladb#24691
This commit is contained in:
committed by
Botond Dénes
parent
1d78f17697
commit
99ec69e27d
@@ -99,6 +99,23 @@ BOOST_AUTO_TEST_CASE(test_big_decimal_construct_from_string) {
|
||||
BOOST_REQUIRE_THROW(big_decimal("+-5"), marshal_exception);
|
||||
BOOST_REQUIRE_THROW(big_decimal("++5"), marshal_exception);
|
||||
BOOST_REQUIRE_THROW(big_decimal("--5"), marshal_exception);
|
||||
|
||||
// Verify large exponent gets parsed correctly
|
||||
// 1E-2147483647 : scale = 2147483647; OK
|
||||
BOOST_REQUIRE_NO_THROW(big_decimal("1E-2147483647"));
|
||||
// 1E2147483648 : scale = -2147483648; OK
|
||||
BOOST_REQUIRE_NO_THROW(big_decimal("1E2147483648"));
|
||||
// 0.01E2147483650 : scale = -2147483648;
|
||||
// exponent is > int32::max() but the adjusted scale is still within int32 limits, so it is OK.
|
||||
BOOST_REQUIRE_NO_THROW(big_decimal("0.01E2147483650"));
|
||||
|
||||
// Any overflow to scale should throw marshal_exception.
|
||||
// 1E-2147483648 : scale(2147483648) > int32::max()
|
||||
BOOST_REQUIRE_THROW(big_decimal("1E-2147483648"), marshal_exception);
|
||||
// 1E2147483649 : scale(-2147483649) < int32::min()
|
||||
BOOST_REQUIRE_THROW(big_decimal("1E2147483649"), marshal_exception);
|
||||
// 1.2E-2147483647 : scale(2147483648) > int32::max()
|
||||
BOOST_REQUIRE_THROW(big_decimal("1.2E-2147483647"), marshal_exception);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_big_decimal_div) {
|
||||
|
||||
@@ -78,12 +78,18 @@ big_decimal::big_decimal(std::string_view text)
|
||||
if (negative) {
|
||||
_unscaled_value *= -1;
|
||||
}
|
||||
// parse scale as int64_t, so that it can be adjusted with fraction size and then checked for overflow.
|
||||
int64_t scale = 0;
|
||||
try {
|
||||
_scale = exponent.empty() ? 0 : -boost::lexical_cast<int32_t>(exponent);
|
||||
scale = exponent.empty() ? 0 : -boost::lexical_cast<int64_t>(exponent);
|
||||
} catch (...) {
|
||||
throw marshal_exception(seastar::format("big_decimal - failed to parse exponent: {}", exponent));
|
||||
}
|
||||
_scale += fraction.size();
|
||||
scale += fraction.size();
|
||||
if (scale < std::numeric_limits<int32_t>::min() || scale > std::numeric_limits<int32_t>::max()) {
|
||||
throw marshal_exception(seastar::format("big_decimal - scale out of range: {}", scale));
|
||||
}
|
||||
_scale = static_cast<int32_t>(scale);
|
||||
}
|
||||
|
||||
boost::multiprecision::cpp_rational big_decimal::as_rational() const {
|
||||
|
||||
Reference in New Issue
Block a user