@@ -130,12 +130,18 @@ Result<Decimal> RescaleHalfUp(const Decimal& unscaled, int32_t from_scale,
130130 return unscaled;
131131 }
132132 if (delta > 0 ) {
133- // Growing the scale multiplies by 10^delta and is exact; Rescale rejects overflow.
133+ // Growing the scale multiplies the coefficient by 10^delta. Report an overflow as an
134+ // invalid argument (consistent with the other rejections here) rather than leaking
135+ // Rescale's "data loss" error to callers.
134136 if (delta > Decimal::kMaxScale ) {
135137 return InvalidArgument (" scale change {} exceeds the maximum {}" , delta,
136138 Decimal::kMaxScale );
137139 }
138- return unscaled.Rescale (from_scale, to_scale);
140+ auto rescaled = unscaled.Rescale (from_scale, to_scale);
141+ if (!rescaled.has_value ()) {
142+ return InvalidArgument (" value does not fit the target decimal scale" );
143+ }
144+ return rescaled.value ();
139145 }
140146 // Shrinking the scale drops `drop` digits with HALF_UP rounding. A drop larger than the
141147 // digits any decimal can hold rounds everything away, so the result is zero (e.g.
@@ -157,6 +163,55 @@ Result<Decimal> RescaleHalfUp(const Decimal& unscaled, int32_t from_scale,
157163 return quotient;
158164}
159165
166+ // Parse a `std::to_chars` double rendering (`[-]digits[.digits][e[+-]exp]`) into an
167+ // integer coefficient and the scale at which it is interpreted (value == coefficient *
168+ // 10^-scale). The coefficient is just the significant digits, so it never overflows
169+ // int128; the exponent is folded into the returned scale rather than expanded, leaving
170+ // RescaleHalfUp to combine it with the target scale.
171+ Result<Decimal> ParseRealCoefficient (std::string_view str, int32_t * scale) {
172+ bool negative = !str.empty () && str.front () == ' -' ;
173+ if (negative) {
174+ str.remove_prefix (1 );
175+ }
176+
177+ int32_t exponent = 0 ;
178+ if (auto e = str.find_first_of (" eE" ); e != std::string_view::npos) {
179+ auto exp_str = str.substr (e + 1 );
180+ // std::to_chars writes a leading '+' for positive exponents, which from_chars
181+ // rejects.
182+ if (!exp_str.empty () && exp_str.front () == ' +' ) {
183+ exp_str.remove_prefix (1 );
184+ }
185+ if (std::from_chars (exp_str.data (), exp_str.data () + exp_str.size (), exponent).ec !=
186+ std::errc{}) {
187+ return InvalidArgument (" Invalid real value '{}'" , str);
188+ }
189+ str = str.substr (0 , e);
190+ }
191+
192+ int32_t frac_digits = 0 ;
193+ std::string digits;
194+ digits.reserve (str.size ());
195+ if (auto dot = str.find (' .' ); dot != std::string_view::npos) {
196+ frac_digits = static_cast <int32_t >(str.size () - dot - 1 );
197+ digits.append (str.substr (0 , dot));
198+ digits.append (str.substr (dot + 1 ));
199+ } else {
200+ digits.append (str);
201+ }
202+ if (digits.empty () || digits.find_first_not_of (" 0123456789" ) != std::string::npos) {
203+ return InvalidArgument (" Invalid real value '{}'" , str);
204+ }
205+
206+ // Coefficient has scale `frac_digits`; a positive base-10 exponent lowers that scale.
207+ *scale = frac_digits - exponent;
208+ ICEBERG_ASSIGN_OR_RAISE (auto coefficient, Decimal::FromString (digits));
209+ if (negative) {
210+ coefficient.Negate ();
211+ }
212+ return coefficient;
213+ }
214+
160215} // namespace
161216
162217Result<Literal> LiteralCaster::CastIntegerToDecimal (
@@ -193,12 +248,18 @@ Result<Literal> LiteralCaster::CastRealToDecimal(
193248 return InvalidArgument (" Cannot cast {} as a {} value" , value,
194249 target_type->ToString ());
195250 }
196- int32_t parsed_scale = 0 ;
251+
252+ // Parse the coefficient and its scale directly instead of via Decimal::FromString,
253+ // which normalizes a negative scale by multiplying the coefficient by 10^-scale and can
254+ // overflow int128 for large magnitudes (e.g. 4e38). The coefficient itself always fits,
255+ // and RescaleHalfUp handles the exponent against the target scale (and rejects
256+ // overflow).
257+ int32_t coeff_scale = 0 ;
197258 ICEBERG_ASSIGN_OR_RAISE (
198- auto parsed ,
199- Decimal::FromString (std::string_view (buf.data (), ptr), nullptr , &parsed_scale ));
259+ auto coefficient ,
260+ ParseRealCoefficient (std::string_view (buf.data (), ptr), &coeff_scale ));
200261
201- ICEBERG_ASSIGN_OR_RAISE (auto unscaled, RescaleHalfUp (parsed, parsed_scale ,
262+ ICEBERG_ASSIGN_OR_RAISE (auto unscaled, RescaleHalfUp (coefficient, coeff_scale ,
202263 decimal_type.scale (), value < 0 ));
203264 if (!unscaled.FitsInPrecision (decimal_type.precision ())) {
204265 return InvalidArgument (" Cannot cast {} as a {} value" , value,
0 commit comments