Skip to content

Convert float literals to wide decimal columns without InvalidOperation - #4372

Open
rahulsmahadev wants to merge 1 commit into
apache:mainfrom
rahulsmahadev:rahul/float-decimal-precision
Open

rahulsmahadev wants to merge 1 commit into
apache:mainfrom
rahulsmahadev:rahul/float-decimal-precision

Conversation

@rahulsmahadev

Copy link
Copy Markdown
Contributor

Rationale for this change

A float filter on a wide decimal column raises decimal.InvalidOperation:

# amt is decimal(38, 2)
tbl.scan(row_filter=GreaterThan("amt", 1e26)).to_arrow()
# decimal.InvalidOperation: [<class 'decimal.InvalidOperation'>]

# amt is decimal(38, 30)
tbl.scan(row_filter=LessThan("amt", 0.5)).to_arrow()
# decimal.InvalidOperation: [<class 'decimal.InvalidOperation'>]

FloatLiteral.to(DecimalType) and DoubleLiteral.to(DecimalType) quantize the value to the column's scale under the default decimal context, which keeps 28 digits. When the quantized value has more digits than that (1e26 with 2 decimals, or 0.5 with 30 decimals), quantize raises. A decimal column can hold up to 38 digits, so these are valid filters.

This change quantizes under a local context with enough precision for the result, in one helper used by both conversions. Rounding is unchanged (ROUND_HALF_UP), and positive and negative infinity still raise the ValueError added in #4287 for #4286. NaN remains rejected when a literal is constructed.

Are these changes tested?

Yes. test_float_to_decimal_conversion_beyond_default_context_precision in tests/expressions/test_literals.py converts 2.0**87 to decimal(38, 2) and 0.5 to decimal(38, 30) from both a FloatLiteral and a DoubleLiteral. It fails on main with decimal.InvalidOperation and passes with this change.

Are there any user-facing changes?

Bug fix: float filters on wide decimal columns no longer raise decimal.InvalidOperation. No API changes.

This pull request and its description were written by Isaac.

FloatLiteral.to(DecimalType) and DoubleLiteral.to(DecimalType) quantize the
value under the default 28-digit decimal context, so a value whose quantized
form has more digits raised decimal.InvalidOperation: amt > 1e26 on a
decimal(38, 2) column, or amt < 0.5 on a decimal(38, 30) column. Quantize under
a context with enough precision for the result.

Co-authored-by: Isaac <no-reply@databricks.com>
@rahulsmahadev

Copy link
Copy Markdown
Contributor Author

@kevinjqliu @AP24110011695 could you take a look when you have a chance? It is the finite-value counterpart of #4287: float filters on wide decimal columns no longer raise decimal.InvalidOperation.

@AP24110011695 AP24110011695 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified failing-first and approving.

On main (fce8185): both cases raise decimal.InvalidOperation —

  • literal(1e26).to(DecimalType(38, 2)) → InvalidOperation
  • literal(0.5).to(DecimalType(38, 30)) → InvalidOperation

On this PR (f0f3520):

  • 1e26 → (38, 2) = 100000000000000004764729344.00 — the exact binary float quantized at the column's scale, ROUND_HALF_UP as claimed
  • 0.5 → (38, 30) = 0.500000000000000000000000000000
  • The #4287 contract is preserved: inf, -inf and nan all still raise the ValueError ("value is not finite") from inside the helper
  • tests/expressions/test_literals.py decimal slice: 32 passed

The shared _float_to_decimal helper also removes the duplicated to(DecimalType) bodies in both literal classes — nice. The context precision (adjusted() + scale + 2) covers the rounding carry with margin to spare.

One non-blocking observation, no change requested: a literal whose quantized form exceeds 38 digits (e.g. 1e26 against decimal(38, 30) → 57 digits) now binds successfully and defers the failure to the scan path, where #4348's widened _literal_arrow_type hits decimal128's 38-digit cap as an ArrowInvalid. That's arguably the right deferral (the literal simply cannot exist in the column), but a clean bind-time ValueError for that case could be a small follow-up if you want bind/scan errors to line up.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants