2222#include < cmath>
2323#include < concepts>
2424#include < cstdint>
25+ #include < format>
2526#include < string>
2627#include < vector>
2728
@@ -89,6 +90,10 @@ class LiteralCaster {
8990 // / Cast an integer value (from Int or Long) to a decimal target type.
9091 static Result<Literal> CastIntegerToDecimal (
9192 int64_t value, const std::shared_ptr<PrimitiveType>& target_type);
93+
94+ // / Cast a floating-point value (from Float or Double) to a decimal target type.
95+ static Result<Literal> CastRealToDecimal (
96+ double value, const std::shared_ptr<PrimitiveType>& target_type);
9297};
9398
9499Literal LiteralCaster::BelowMinLiteral (std::shared_ptr<PrimitiveType> type) {
@@ -120,6 +125,50 @@ Result<Literal> LiteralCaster::CastIntegerToDecimal(
120125 decimal_type.scale ());
121126}
122127
128+ Result<Literal> LiteralCaster::CastRealToDecimal (
129+ double value, const std::shared_ptr<PrimitiveType>& target_type) {
130+ const auto & decimal_type = internal::checked_cast<const DecimalType&>(*target_type);
131+ const int32_t target_scale = decimal_type.scale ();
132+ if (target_scale < 0 || target_scale > Decimal::kMaxScale ) {
133+ return InvalidArgument (" Cannot cast {} as a {} value" , value,
134+ target_type->ToString ());
135+ }
136+ if (!std::isfinite (value)) {
137+ return InvalidArgument (" Cannot cast {} as a {} value" , value,
138+ target_type->ToString ());
139+ }
140+
141+ // Parse the shortest round-tripping decimal representation of the value (matching
142+ // Java's BigDecimal.valueOf(double), which goes through Double.toString) into a
143+ // full-precision decimal, then round to the target scale below.
144+ int32_t parsed_scale = 0 ;
145+ ICEBERG_ASSIGN_OR_RAISE (
146+ auto parsed, Decimal::FromString (std::format (" {}" , value), nullptr , &parsed_scale));
147+
148+ Decimal unscaled = parsed;
149+ if (parsed_scale > target_scale) {
150+ // Drop excess fractional digits with HALF_UP rounding (round half away from zero, as
151+ // Java does), since Rescale itself only truncates and rejects any dropped remainder.
152+ const int32_t drop = parsed_scale - target_scale;
153+ ICEBERG_ASSIGN_OR_RAISE (auto divisor, Decimal (1 ).Rescale (0 , drop));
154+ ICEBERG_ASSIGN_OR_RAISE (auto divmod, parsed.Divide (divisor));
155+ Decimal quotient = divmod.first ;
156+ Decimal remainder = Decimal::Abs (divmod.second );
157+ if (remainder * Decimal (2 ) >= divisor) {
158+ quotient += (value < 0 ) ? Decimal (-1 ) : Decimal (1 );
159+ }
160+ unscaled = quotient;
161+ } else if (parsed_scale < target_scale) {
162+ ICEBERG_ASSIGN_OR_RAISE (unscaled, parsed.Rescale (parsed_scale, target_scale));
163+ }
164+
165+ if (!unscaled.FitsInPrecision (decimal_type.precision ())) {
166+ return InvalidArgument (" Cannot cast {} as a {} value" , value,
167+ target_type->ToString ());
168+ }
169+ return Literal::Decimal (unscaled.value (), decimal_type.precision (), target_scale);
170+ }
171+
123172Result<Literal> LiteralCaster::CastFromInt (
124173 const Literal& literal, const std::shared_ptr<PrimitiveType>& target_type) {
125174 auto int_val = std::get<int32_t >(literal.value_ );
@@ -195,6 +244,8 @@ Result<Literal> LiteralCaster::CastFromFloat(
195244 switch (target_type->type_id ()) {
196245 case TypeId::kDouble :
197246 return Literal::Double (static_cast <double >(float_val));
247+ case TypeId::kDecimal :
248+ return CastRealToDecimal (static_cast <double >(float_val), target_type);
198249 default :
199250 return NotSupported (" Cast from Float to {} is not supported" ,
200251 target_type->ToString ());
@@ -215,6 +266,8 @@ Result<Literal> LiteralCaster::CastFromDouble(
215266 }
216267 return Literal::Float (static_cast <float >(double_val));
217268 }
269+ case TypeId::kDecimal :
270+ return CastRealToDecimal (double_val, target_type);
218271 default :
219272 return NotSupported (" Cast from Double to {} is not supported" ,
220273 target_type->ToString ());
0 commit comments