This is an automated email from the ASF dual-hosted git repository.

Jefffrey pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git


The following commit(s) were added to refs/heads/main by this push:
     new 8acab7b537 feat: Implement decimal <-> float16 casts (#10008)
8acab7b537 is described below

commit 8acab7b5371470deff6c211899295d2bb3030dfc
Author: Neil Conway <[email protected]>
AuthorDate: Sun May 24 21:57:45 2026 -0400

    feat: Implement decimal <-> float16 casts (#10008)
    
    # Which issue does this PR close?
    
    - Closes #9123.
    
    # Rationale for this change
    
    Arrow supports casts between decimal and float64/float32; for
    consistency and completeness, we should also support casts between
    decimal and float16.
    
    In DataFusion, this will be particularly useful: once
    https://github.com/apache/datafusion/issues/14612 is fixed,
    `arrow_cast(0.0, 'Float16')` will no longer work, unless we first add
    support for decimal -> float16 casts in arrow-rs.
    
    # What changes are included in this PR?
    
    * Add support for decimal -> float16 cast
    * Add support for float16 -> decimal cast
    * Add unit tests for new behavior
    * Update docs/comment on supported casts
    
    # Are these changes tested?
    
    Yes; new tests added.
    
    # Are there any user-facing changes?
    
    Yes; new casts are now supported. Otherwise no changes.
---
 arrow-cast/src/cast/mod.rs | 282 ++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 278 insertions(+), 4 deletions(-)

diff --git a/arrow-cast/src/cast/mod.rs b/arrow-cast/src/cast/mod.rs
index 6b4355ae3a..12442bd9fd 100644
--- a/arrow-cast/src/cast/mod.rs
+++ b/arrow-cast/src/cast/mod.rs
@@ -188,7 +188,7 @@ pub fn can_cast_types(from_type: &DataType, to_type: 
&DataType) -> bool {
         ) => true,
         // signed numeric to decimal
         (
-            Int8 | Int16 | Int32 | Int64 | Float32 | Float64,
+            Int8 | Int16 | Int32 | Int64 | Float16 | Float32 | Float64,
             Decimal32(_, _) | Decimal64(_, _) | Decimal128(_, _) | 
Decimal256(_, _),
         ) => true,
         // decimal to unsigned numeric
@@ -199,7 +199,7 @@ pub fn can_cast_types(from_type: &DataType, to_type: 
&DataType) -> bool {
         // decimal to signed numeric
         (
             Decimal32(_, _) | Decimal64(_, _) | Decimal128(_, _) | 
Decimal256(_, _),
-            Null | Int8 | Int16 | Int32 | Int64 | Float32 | Float64,
+            Null | Int8 | Int16 | Int32 | Int64 | Float16 | Float32 | Float64,
         ) => true,
         // decimal to string
         (
@@ -649,9 +649,9 @@ fn timestamp_to_date32<T: ArrowTimestampType>(
 /// * `Time32 and `Time64`: precision lost when going to higher interval
 /// * `Timestamp` and `Date{32|64}`: precision lost when going to higher 
interval
 /// * Temporal to/from backing Primitive: zero-copy with data type change
-/// * `Float32/Float64` to `Decimal(precision, scale)` rounds to the `scale` 
decimals
+/// * `Float16/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
+/// * `Decimal` to `Float16/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):
@@ -2329,6 +2329,13 @@ where
         Int16 => cast_decimal_to_integer::<D, Int16Type>(array, base, *scale, 
cast_options),
         Int32 => cast_decimal_to_integer::<D, Int32Type>(array, base, *scale, 
cast_options),
         Int64 => cast_decimal_to_integer::<D, Int64Type>(array, base, *scale, 
cast_options),
+        Float16 => cast_decimal_to_float::<D, Float16Type, _>(array, |x| {
+            half::f16::from_f64(single_decimal_to_float_lossy::<D, F>(
+                &as_float,
+                x,
+                <i32 as From<i8>>::from(*scale),
+            ))
+        }),
         Float32 => cast_decimal_to_float::<D, Float32Type, _>(array, |x| {
             single_decimal_to_float_lossy::<D, F>(&as_float, x, <i32 as 
From<i8>>::from(*scale))
                 as f32
@@ -2426,6 +2433,12 @@ where
             base,
             cast_options,
         ),
+        Float16 => cast_floating_point_to_decimal::<_, D>(
+            array.as_primitive::<Float16Type>(),
+            *precision,
+            *scale,
+            cast_options,
+        ),
         Float32 => cast_floating_point_to_decimal::<_, D>(
             array.as_primitive::<Float32Type>(),
             *precision,
@@ -3366,6 +3379,84 @@ mod tests {
         }
     }
 
+    #[test]
+    fn test_cast_float16_to_decimals() {
+        let array = Float16Array::from(vec![
+            Some(f16::from_f32(1.25)),
+            Some(f16::from_f32(-2.5)),
+            Some(f16::from_f32(1.125)),
+            Some(f16::from_f32(-1.125)),
+            Some(f16::from_f32(0.0)),
+            None,
+        ]);
+
+        generate_cast_test_case!(
+            &array,
+            Decimal32Array,
+            &DataType::Decimal32(9, 2),
+            vec![
+                Some(125_i32),
+                Some(-250_i32),
+                Some(113_i32),
+                Some(-113_i32),
+                Some(0_i32),
+                None
+            ]
+        );
+        generate_cast_test_case!(
+            &array,
+            Decimal64Array,
+            &DataType::Decimal64(18, 2),
+            vec![
+                Some(125_i64),
+                Some(-250_i64),
+                Some(113_i64),
+                Some(-113_i64),
+                Some(0_i64),
+                None
+            ]
+        );
+        generate_cast_test_case!(
+            &array,
+            Decimal128Array,
+            &DataType::Decimal128(38, 2),
+            vec![
+                Some(125_i128),
+                Some(-250_i128),
+                Some(113_i128),
+                Some(-113_i128),
+                Some(0_i128),
+                None
+            ]
+        );
+        generate_cast_test_case!(
+            &array,
+            Decimal256Array,
+            &DataType::Decimal256(76, 2),
+            vec![
+                Some(i256::from_i128(125_i128)),
+                Some(i256::from_i128(-250_i128)),
+                Some(i256::from_i128(113_i128)),
+                Some(i256::from_i128(-113_i128)),
+                Some(i256::from_i128(0_i128)),
+                None
+            ]
+        );
+
+        let array = Float16Array::from(vec![
+            Some(f16::from_f32(1250.0)),
+            Some(f16::from_f32(-1250.0)),
+            Some(f16::from_f32(1249.0)),
+            None,
+        ]);
+        generate_cast_test_case!(
+            &array,
+            Decimal128Array,
+            &DataType::Decimal128(5, -2),
+            vec![Some(13_i128), Some(-13_i128), Some(12_i128), None]
+        );
+    }
+
     #[test]
     fn test_cast_decimal128_to_decimal128_overflow() {
         let input_type = DataType::Decimal128(38, 3);
@@ -3620,6 +3711,19 @@ mod tests {
             &DataType::Int64,
             vec![Some(1_i64), Some(2_i64), Some(3_i64), None, Some(5_i64)]
         );
+        // f16
+        generate_cast_test_case!(
+            array,
+            Float16Array,
+            &DataType::Float16,
+            vec![
+                Some(f16::from_f32(1.25)),
+                Some(f16::from_f32(2.25)),
+                Some(f16::from_f32(3.25)),
+                None,
+                Some(f16::from_f32(5.25))
+            ]
+        );
         // f32
         generate_cast_test_case!(
             array,
@@ -3847,6 +3951,19 @@ mod tests {
             &DataType::Int64,
             vec![Some(1_i64), Some(2_i64), Some(3_i64), None, Some(5_i64)]
         );
+        // f16
+        generate_cast_test_case!(
+            &array,
+            Float16Array,
+            &DataType::Float16,
+            vec![
+                Some(f16::from_f32(1.25)),
+                Some(f16::from_f32(2.25)),
+                Some(f16::from_f32(3.25)),
+                None,
+                Some(f16::from_f32(5.25))
+            ]
+        );
         // f32
         generate_cast_test_case!(
             &array,
@@ -3957,6 +4074,60 @@ mod tests {
         );
     }
 
+    #[test]
+    fn test_cast_decimal128_to_float16_overflow() {
+        let array = create_decimal128_array(
+            vec![
+                Some(6_550_400_i128),
+                Some(100_000_000_i128),
+                Some(-100_000_000_i128),
+                None,
+            ],
+            10,
+            2,
+        )
+        .unwrap();
+
+        generate_cast_test_case!(
+            &array,
+            Float16Array,
+            &DataType::Float16,
+            vec![
+                Some(f16::from_f64(65504.0)),
+                Some(f16::INFINITY),
+                Some(f16::NEG_INFINITY),
+                None
+            ]
+        );
+    }
+
+    #[test]
+    fn test_cast_decimal256_to_float16_overflow() {
+        let array = create_decimal256_array(
+            vec![
+                Some(i256::from_i128(6_550_400_i128)),
+                Some(i256::from_i128(100_000_000_i128)),
+                Some(i256::from_i128(-100_000_000_i128)),
+                None,
+            ],
+            10,
+            2,
+        )
+        .unwrap();
+
+        generate_cast_test_case!(
+            &array,
+            Float16Array,
+            &DataType::Float16,
+            vec![
+                Some(f16::from_f64(65504.0)),
+                Some(f16::INFINITY),
+                Some(f16::NEG_INFINITY),
+                None
+            ]
+        );
+    }
+
     #[test]
     fn test_cast_decimal_to_numeric_negative_scale() {
         let value_array: Vec<Option<i256>> = vec![
@@ -3975,6 +4146,19 @@ mod tests {
             vec![Some(1_250), Some(2_250), Some(3_250), None, Some(5_250)]
         );
 
+        let value_array: Vec<Option<i128>> = vec![Some(12), Some(-12), None];
+        let array = create_decimal128_array(value_array, 10, -2).unwrap();
+        generate_cast_test_case!(
+            &array,
+            Float16Array,
+            &DataType::Float16,
+            vec![
+                Some(f16::from_f32(1200.0)),
+                Some(f16::from_f32(-1200.0)),
+                None
+            ]
+        );
+
         let value_array: Vec<Option<i32>> = vec![Some(125), Some(225), 
Some(325), None, Some(525)];
         let array = create_decimal32_array(value_array, 8, -2).unwrap();
         generate_cast_test_case!(
@@ -10164,6 +10348,96 @@ mod tests {
         );
     }
 
+    #[test]
+    fn test_cast_float16_to_decimal128_precision_overflow() {
+        let array = Float16Array::from(vec![f16::from_f32(1.1)]);
+        let array = Arc::new(array) as ArrayRef;
+        let casted_array = cast_with_options(
+            &array,
+            &DataType::Decimal128(2, 2),
+            &CastOptions {
+                safe: true,
+                format_options: FormatOptions::default(),
+            },
+        );
+        assert!(casted_array.is_ok());
+        assert!(casted_array.unwrap().is_null(0));
+
+        let casted_array = cast_with_options(
+            &array,
+            &DataType::Decimal128(2, 2),
+            &CastOptions {
+                safe: false,
+                format_options: FormatOptions::default(),
+            },
+        );
+        let err = casted_array.unwrap_err().to_string();
+        let expected_error = "Invalid argument error: 1.10 is too large to 
store in a Decimal128 of precision 2. Max is 0.99";
+        assert_eq!(err, expected_error);
+    }
+
+    #[test]
+    fn test_cast_float16_to_decimal256_precision_overflow() {
+        let array = Float16Array::from(vec![f16::from_f32(1.1)]);
+        let array = Arc::new(array) as ArrayRef;
+        let casted_array = cast_with_options(
+            &array,
+            &DataType::Decimal256(2, 2),
+            &CastOptions {
+                safe: true,
+                format_options: FormatOptions::default(),
+            },
+        );
+        assert!(casted_array.is_ok());
+        assert!(casted_array.unwrap().is_null(0));
+
+        let casted_array = cast_with_options(
+            &array,
+            &DataType::Decimal256(2, 2),
+            &CastOptions {
+                safe: false,
+                format_options: FormatOptions::default(),
+            },
+        );
+        let err = casted_array.unwrap_err().to_string();
+        let expected_error = "Invalid argument error: 1.10 is too large to 
store in a Decimal256 of precision 2. Max is 0.99";
+        assert_eq!(err, expected_error);
+    }
+
+    #[test]
+    fn test_cast_float16_to_decimal128_non_finite() {
+        let array = Float16Array::from(vec![f16::NAN, f16::INFINITY, 
f16::NEG_INFINITY]);
+        let array = Arc::new(array) as ArrayRef;
+        let casted_array = cast_with_options(
+            &array,
+            &DataType::Decimal128(38, 2),
+            &CastOptions {
+                safe: true,
+                format_options: FormatOptions::default(),
+            },
+        )
+        .unwrap();
+
+        assert!(casted_array.is_null(0));
+        assert!(casted_array.is_null(1));
+        assert!(casted_array.is_null(2));
+
+        let casted_array = cast_with_options(
+            &array,
+            &DataType::Decimal128(38, 2),
+            &CastOptions {
+                safe: false,
+                format_options: FormatOptions::default(),
+            },
+        );
+        let err = casted_array.unwrap_err().to_string();
+        let expected_error = "Cannot cast to Decimal128(38, 2)";
+        assert!(
+            err.contains(expected_error),
+            "did not find expected error '{expected_error}' in actual error 
'{err}'"
+        );
+    }
+
     #[test]
     fn test_cast_floating_point_to_decimal256_precision_overflow() {
         let array = Float64Array::from(vec![1.1]);

Reply via email to