You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iceberg.apache.org by li...@apache.org on 2026/07/09 09:13:21 UTC

(iceberg-rust) branch main updated: fix(spec): support float/double conversion in Datum::to() (#2783)

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

blackmwk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-rust.git


The following commit(s) were added to refs/heads/main by this push:
     new ff678ca6d fix(spec): support float/double conversion in Datum::to() (#2783)
ff678ca6d is described below

commit ff678ca6d1123d9d82902b34379ccba2caf172b6
Author: Matt Butrovich <mb...@users.noreply.github.com>
AuthorDate: Thu Jul 9 05:13:15 2026 -0400

    fix(spec): support float/double conversion in Datum::to() (#2783)
    
    ## Which issue does this PR close?
    
    - Closes #.
    
    ## What changes are included in this PR?
    
    `Datum::to()` had no conversion between `float` and `double`, so binding
    a predicate whose literal type differed from the column type failed with
    `Can't convert datum from double type to float type`. This is common
    because engines often carry a floating point literal as a double even
    when the column is a float.
    
    This PR adds the two missing arms:
    
    - `Double -> Float`: narrows through a new `f64_to_f32` helper that
    returns `AboveMax` / `BelowMin` when the value is outside the finite
    float range, matching the existing integer narrowing helpers
    (`i64_to_i32`, `i128_to_i64`). A plain `as f32` cast would saturate to
    `+/-inf` and change comparison results, so the sentinels let predicate
    binding resolve the comparison correctly.
    - `Float -> Double`: widens losslessly.
    
    ## Are these changes tested?
    
    Yes. Four unit tests in `spec/values/tests.rs` cover the in range
    narrowing, the above max and below min cases, and the widening. The
    `AboveMax` / `BelowMin` handling is already exercised by the existing
    predicate binding tests in `expr::predicate`.
    
    ---------
    
    Co-authored-by: emkornfield <em...@gmail.com>
    Co-authored-by: blackmwk <li...@outlook.com>
---
 crates/iceberg/src/spec/values/datum.rs | 16 ++++++++++++
 crates/iceberg/src/spec/values/tests.rs | 44 +++++++++++++++++++++++++++++++++
 2 files changed, 60 insertions(+)

diff --git a/crates/iceberg/src/spec/values/datum.rs b/crates/iceberg/src/spec/values/datum.rs
index 2040d4634..9e7b9a19e 100644
--- a/crates/iceberg/src/spec/values/datum.rs
+++ b/crates/iceberg/src/spec/values/datum.rs
@@ -1086,6 +1086,16 @@ impl Datum {
         }
     }
 
+    fn f64_to_f32(val: f64) -> Datum {
+        if val > f32::MAX as f64 {
+            Datum::new(PrimitiveType::Float, PrimitiveLiteral::AboveMax)
+        } else if val < (f32::MIN as f64) {
+            Datum::new(PrimitiveType::Float, PrimitiveLiteral::BelowMin)
+        } else {
+            Datum::float(val as f32)
+        }
+    }
+
     fn string_to_i128<S: AsRef<str>>(s: S) -> Result<i128> {
         s.as_ref().parse::<i128>().map_err(|e| {
             Error::new(ErrorKind::DataInvalid, "Can't parse string to i128.").with_source(e)
@@ -1123,6 +1133,12 @@ impl Datum {
                     (PrimitiveLiteral::Long(val), _, PrimitiveType::Int) => {
                         Ok(Datum::i64_to_i32(*val))
                     }
+                    (PrimitiveLiteral::Double(val), _, PrimitiveType::Float) => {
+                        Ok(Datum::f64_to_f32(val.0))
+                    }
+                    (PrimitiveLiteral::Float(val), _, PrimitiveType::Double) => {
+                        Ok(Datum::double(val.0 as f64))
+                    }
                     (PrimitiveLiteral::Long(val), _, PrimitiveType::Timestamp) => {
                         Ok(Datum::timestamp_micros(*val))
                     }
diff --git a/crates/iceberg/src/spec/values/tests.rs b/crates/iceberg/src/spec/values/tests.rs
index bc8b011fd..a8a6716f9 100644
--- a/crates/iceberg/src/spec/values/tests.rs
+++ b/crates/iceberg/src/spec/values/tests.rs
@@ -1183,6 +1183,50 @@ fn test_datum_long_convert_to_int_below_min() {
     assert_eq!(result, expected);
 }
 
+#[test]
+fn test_datum_double_convert_to_float() {
+    let datum = Datum::double(2.5);
+
+    let result = datum.to(&Primitive(PrimitiveType::Float)).unwrap();
+
+    let expected = Datum::float(2.5);
+
+    assert_eq!(result, expected);
+}
+
+#[test]
+fn test_datum_double_convert_to_float_above_max() {
+    let datum = Datum::double(1e39);
+
+    let result = datum.to(&Primitive(PrimitiveType::Float)).unwrap();
+
+    let expected = Datum::new(PrimitiveType::Float, PrimitiveLiteral::AboveMax);
+
+    assert_eq!(result, expected);
+}
+
+#[test]
+fn test_datum_double_convert_to_float_below_min() {
+    let datum = Datum::double(-1e39);
+
+    let result = datum.to(&Primitive(PrimitiveType::Float)).unwrap();
+
+    let expected = Datum::new(PrimitiveType::Float, PrimitiveLiteral::BelowMin);
+
+    assert_eq!(result, expected);
+}
+
+#[test]
+fn test_datum_float_convert_to_double() {
+    let datum = Datum::float(2.5);
+
+    let result = datum.to(&Primitive(PrimitiveType::Double)).unwrap();
+
+    let expected = Datum::double(2.5);
+
+    assert_eq!(result, expected);
+}
+
 #[test]
 fn test_datum_long_convert_to_timestamp() {
     let datum = Datum::long(12345);