[AURON #2475] Fix float-to-decimal precision loss - #2496
Conversation
Signed-off-by: Ma Zhengxuan <1319614897@qq.com>
There was a problem hiding this comment.
Pull request overview
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Fixes Spark-incompatible float/double → decimal casting by avoiding Arrow’s float scaling logic that can introduce precision loss at high decimal scales.
Changes:
- Add Spark-compatible Float32/Float64 → Decimal128 casting by formatting floats to strings, then reusing the existing string→decimal path.
- Treat NULL/NaN/±Inf and precision-overflow as NULL during float→decimal casts.
- Re-enable the SPARK-22271 regression suite exclusions for Spark 3.1–3.5 and add Rust regression tests.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| native-engine/datafusion-ext-commons/src/arrow/cast.rs | Implements the new float→decimal cast path and adds Rust regression coverage. |
| auron-spark-tests/spark35/src/test/scala/org/apache/auron/utils/AuronSparkTestSettings.scala | Re-enables SPARK-22271 by removing the exclusion. |
| auron-spark-tests/spark34/src/test/scala/org/apache/auron/utils/AuronSparkTestSettings.scala | Re-enables SPARK-22271 by removing the exclusion. |
| auron-spark-tests/spark33/src/test/scala/org/apache/auron/utils/AuronSparkTestSettings.scala | Re-enables SPARK-22271 by removing the exclusion. |
| auron-spark-tests/spark32/src/test/scala/org/apache/auron/utils/AuronSparkTestSettings.scala | Re-enables SPARK-22271 by removing the exclusion. |
| auron-spark-tests/spark31/src/test/scala/org/apache/auron/utils/AuronSparkTestSettings.scala | Re-enables SPARK-22271 by removing the exclusion. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| // spark compatible float to decimal | ||
| (&DataType::Float32, DataType::Decimal128(..)) | ||
| | (&DataType::Float64, DataType::Decimal128(..)) => { | ||
| let floats = arrow::compute::cast(array, &DataType::Float64)?; | ||
| let strings = floats | ||
| .as_primitive::<Float64Type>() | ||
| .iter() | ||
| .map(|value| { | ||
| value | ||
| .filter(|value| value.is_finite()) | ||
| .map(|value| value.to_string()) | ||
| }) | ||
| .collect::<StringArray>(); | ||
| cast_impl(&strings, cast_type, match_struct_fields)? | ||
| } |
There was a problem hiding this comment.
Updated the comment to explain the shortest round-trip conversion, exact decimal rescaling, and non-finite-to-NULL behavior.
| (&DataType::Float32, DataType::Decimal128(..)) | ||
| | (&DataType::Float64, DataType::Decimal128(..)) => { | ||
| let floats = arrow::compute::cast(array, &DataType::Float64)?; | ||
| let strings = floats | ||
| .as_primitive::<Float64Type>() | ||
| .iter() | ||
| .map(|value| { | ||
| value | ||
| .filter(|value| value.is_finite()) | ||
| .map(|value| value.to_string()) | ||
| }) | ||
| .collect::<StringArray>(); | ||
| cast_impl(&strings, cast_type, match_struct_fields)? | ||
| } |
| #[test] | ||
| fn test_float_to_decimal() -> Result<()> { | ||
| let f64_array: ArrayRef = Arc::new(Float64Array::from_iter(vec![ | ||
| Some(0.034567890), | ||
| None, | ||
| Some(f64::NAN), | ||
| Some(f64::INFINITY), | ||
| Some(f64::NEG_INFINITY), | ||
| Some(1e40), | ||
| ])); |
There was a problem hiding this comment.
Added focused coverage for half-up rounding with positive and negative values, as well as signed zero.
Signed-off-by: Ma Zhengxuan <1319614897@qq.com>
|
@Sigma-Ma Thanks for the contribution! Merged into the master. |
Which issue does this PR close?
Closes #2475
Rationale for this change
Auron currently delegates
Float32/Float64 -> Decimal128casts to Arrow's native cast kernel. That kernel scales the input by10^scaleusing floating-point arithmetic, which introduces incorrect digits at high decimal scales.Spark instead converts the floating-point value to its shortest round-trip decimal representation before rescaling it exactly.
What changes are included in this PR?
Float32/Float64 -> Decimal128path incast_impl.Float32values toFloat64before formatting, matching Spark behavior.SPARK-22271regression test for Spark 3.1 through 3.5.Are there any user-facing changes?
Bug fix only. Casting float or double values to decimal now matches Spark at high decimal scales. There are no public API or configuration changes.
How was this patch tested?
cargo test -p datafusion-ext-commons test_float_to_decimal -- --nocapturecargo test -p datafusion-ext-commons./dev/reformat --check./auron-build.sh --pre --sparkver 3.5 --scalaver 2.12 --skiptests trueWas this patch authored or co-authored using generative AI tooling?
Generated-by: OpenAI Codex (GPT-5)
ASF guidance: https://www.apache.org/legal/generative-tooling.html