Repository navigation
fix(polars): coerce to Decimal without a Float64 round trip - #2574
Merged
cosmicbboy merged 1 commit intoOct 16, 2026
Merged
Conversation
Decimal.coerce cast every column to Float64 before casting to the target decimal, so strings, integers and wider decimals lost digits: "16809.76" became 16809.75 on polars 1.33.1 and earlier, and 123456789012345678 became 123456789012345680 on every polars version. Integers and decimals are now cast to the target decimal directly. Strings are parsed directly and fall back to the previous Float64 path only for values the direct parse rejects, so inputs that coerced before (for example scientific notation on older polars) still coerce. Floats and booleans keep the Float64 path. Fixes unionai-oss#2109 Signed-off-by: Dhruv Gupta <[email protected]>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #2574 +/- ##
=======================================
Coverage 82.70% 82.71%
=======================================
Files 240 240
Lines 21215 21226 +11
=======================================
+ Hits 17546 17557 +11
Misses 3669 3669 Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
cosmicbboy
approved these changes
Oct 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #2109.
Summary
Decimal.coercein the polars engine casts the column toFloat64before casting it to the target decimal, so any value that does not survive a float round trip loses digits:Polars can cast integers, decimals and strings to
Decimaldirectly, so the float step is not needed for them.Changes
pandera/engines/polars_engine.py:Decimal.coercenow picks the cast per column dtype:Float64path. Scientific notation ("1e3") is the case that needs this: on polars 1.20.0 and 1.33.1 the direct string-to-decimal cast rejects it, while the float path accepts it;Float64path (polars does not castBooleantoDecimaldirectly).tests/polars/test_polars_dtypes.py:test_coerce_decimal_keeps_precision: strings, large integers, a wider decimal, scientific notation, floats and booleans.test_coerce_decimal_keeps_precision_in_schema: the schema-level example from the issue. It is markedxfailunder the narwhals backend, which does not applycoerce=True, the same way as the existing coercion tests intests/polars/test_polars_container.py.test_try_coerce_decimal_invalid_string: a non-numeric string still raisesParserErrorwith that value as the failure case.Tests
main: 5 failed on polars 1.20.0, 4 failed on polars 2.0.0 (where"16809.76"happens to round correctly through the float).pytest tests/polarswith this change:PANDERA_USE_NARWHALS_BACKEND=True: 410 passed, 1 skipped, 75 xfailedtest_dataframe_model_with_annotated_nested_typesandtest_polars_pydantic.py::test_conversion_types) also fail on unmodifiedmainwith polars 2.0.0 and are not related to this change.pytest tests/base: 19 passed.prek run --files pandera/engines/polars_engine.py tests/polars/test_polars_dtypes.py: all hooks pass (ruff, ruff format, mypy, pyupgrade, flynt, codespell).Backward compatibility
No existing tests were changed. The user-visible change is that coercing integer, decimal and string columns to
pl.Decimalnow keeps the exact input value instead of the nearest float.Float64path is still evaluated as the fallback, so every string that coerced before still coerces, and strings that failed before still fail with the same polars error.999999999999999999intopl.Decimal(18, 0).