feat(expression): cast numeric literals to decimal type#805
Conversation
| // Drop excess fractional digits with HALF_UP rounding (round half away from zero, as | ||
| // Java does), since Rescale itself only truncates and rejects any dropped remainder. | ||
| const int32_t drop = parsed_scale - target_scale; | ||
| ICEBERG_ASSIGN_OR_RAISE(auto divisor, Decimal(1).Rescale(0, drop)); |
There was a problem hiding this comment.
Please guard drop before calling Rescale. For values like 1e-100 cast to decimal(9,2), drop is 98 and indexes past the powers-of-ten table; Java rounds this to 0.00.
There was a problem hiding this comment.
Fixed. Rounding now goes through a shared RescaleHalfUp helper that returns 0 when the drop exceeds the max scale (so 1e-100 -> 0.00 instead of indexing past the table). Added a regression test.
| const int32_t scale = decimal_type.scale(); | ||
| // DecimalType does not bound its scale, but Rescale indexes a powers-of-ten table sized | ||
| // for [0, kMaxScale]; reject an out-of-range scale here rather than reading past it. | ||
| if (scale < 0 || scale > Decimal::kMaxScale) { |
There was a problem hiding this comment.
Please allow negative decimal scales here and in the real-value path. Iceberg decimal(P,S) supports negative S, and Java uses setScale(scale, HALF_UP), so 149 -> decimal(9,-2) should round to 1E+2 instead of failing.
There was a problem hiding this comment.
Done — both the integer and real paths now allow negative scales and round HALF_UP, so 149 -> decimal(9,-2) gives 1E+2. Tested for both int and double sources.
e5e3610 to
09a45b6
Compare
What
Add casting of numeric literals (
int,long,float,double) to a decimal target type inLiteral::CastTo, so a numeric value can be used as a default for a decimal column.Previously
CastFromInt/CastFromLong/CastFromFloat/CastFromDoublehad nokDecimalcase and fell through toNotSupported, so a default likeLiteral::Int(12)orLiteral::Double(9.99)for adecimal(9, 2)column was rejected. Java allows these (IntegerLiteral.to,DoubleLiteral.to, etc. scale the value to the target scale), so this brings the C++ literal cast layer to parity for numeric sources.How
CastIntegerToDecimalscales the integer (scale 0) up to the target scale viaDecimal::Rescale(0, scale), then verifies the result fits the target precision (FitsInPrecision). Example:12→decimal(9,2)yields unscaled1200(12.00).CastRealToDecimalparses the value's shortest round-tripping decimal representation (matching Java'sBigDecimal.valueOf(double)viaDouble.toString), then rounds to the target scale with HALF_UP rounding (round half away from zero, as Java does —2.5→3,-2.5→-3), and checks precision. Non-finite values are rejected.DecimalTypedoes not bound its scale on construction, sodecimal(9, 40)would otherwise read past the table).Scope
Follow-up split out from the v3 default-value work (see the
CastDefaultToTypediscussion on #793). It only extends the sharedLiteral::CastTolayer for numeric sources.Testing
LiteralTest.IntegerCastToDecimal(int/long scaling, out-of-precision rejection, out-of-range scale rejection) andLiteralTest.RealCastToDecimal(float/double scaling, HALF_UP rounding incl. negative, round-down, out-of-precision and non-finite rejection), all verified fail-without / pass-with. Fullexpression_testpasses (495 tests).