diff --git a/arrow-cast/src/cast/decimal.rs b/arrow-cast/src/cast/decimal.rs index 57dfc51d74c8..597f384fa452 100644 --- a/arrow-cast/src/cast/decimal.rs +++ b/arrow-cast/src/cast/decimal.rs @@ -614,7 +614,11 @@ where Ok(Arc::new(value_builder.finish())) } -// Cast the decimal array to floating-point array +/// Cast a decimal array to a floating point array. +/// +/// Conversion is lossy and follows standard floating point semantics. Values +/// that exceed the representable range become `INFINITY` or `-INFINITY` without +/// returning an error. pub(crate) fn cast_decimal_to_float( array: &dyn Array, op: F, diff --git a/arrow-cast/src/cast/mod.rs b/arrow-cast/src/cast/mod.rs index 884a32197c99..aad20e86fbe7 100644 --- a/arrow-cast/src/cast/mod.rs +++ b/arrow-cast/src/cast/mod.rs @@ -603,6 +603,8 @@ fn timestamp_to_date32( /// * Temporal to/from backing Primitive: zero-copy with data type change /// * `Float32/Float64` to `Decimal(precision, scale)` rounds to the `scale` decimals /// (i.e. casting `6.4999` to `Decimal(10, 1)` becomes `6.5`). +/// * `Decimal` to `Float32/Float64` is lossy and values outside the representable +/// range become `INFINITY` or `-INFINITY` without error. /// /// Unsupported Casts (check with `can_cast_types` before calling): /// * To or from `StructArray` @@ -891,7 +893,7 @@ pub fn cast_with_options( scale, from_type, to_type, - |x: i256| x.to_f64().unwrap(), + |x: i256| decimal256_to_f64(x), cast_options, ) } @@ -1993,6 +1995,17 @@ where } } +/// Convert a [`i256`] to `f64` saturating to infinity on overflow. +fn decimal256_to_f64(v: i256) -> f64 { + v.to_f64().unwrap_or_else(|| { + if v.is_negative() { + f64::NEG_INFINITY + } else { + f64::INFINITY + } + }) +} + fn cast_to_decimal( array: &dyn Array, base: M, @@ -8660,6 +8673,28 @@ mod tests { "did not find expected error '{expected_error}' in actual error '{err}'" ); } + #[test] + fn test_cast_decimal256_to_f64_overflow() { + // Test positive overflow (positive infinity) + let array = vec![Some(i256::MAX)]; + let array = create_decimal256_array(array, 76, 2).unwrap(); + let array = Arc::new(array) as ArrayRef; + + let result = cast(&array, &DataType::Float64).unwrap(); + let result = result.as_primitive::(); + assert!(result.value(0).is_infinite()); + assert!(result.value(0) > 0.0); // Positive infinity + + // Test negative overflow (negative infinity) + let array = vec![Some(i256::MIN)]; + let array = create_decimal256_array(array, 76, 2).unwrap(); + let array = Arc::new(array) as ArrayRef; + + let result = cast(&array, &DataType::Float64).unwrap(); + let result = result.as_primitive::(); + assert!(result.value(0).is_infinite()); + assert!(result.value(0) < 0.0); // Negative infinity + } #[test] fn test_cast_decimal128_to_decimal128_negative_scale() {