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

github-merge-queue[bot] pushed a commit to branch 
gh-readonly-queue/main/pr-3476-7c39c3748d2351cdb8b2b5432937c96766fc07bf
in repository https://gitbox.apache.org/repos/asf/iceberg-rust.git

commit 217e66dbdf2e206b843e6e68acd85fb4feeeea7b
Author: Spicy Shark <[email protected]>
AuthorDate: Sat Oct 24 08:52:39 2026 +0000

    fix(spec): read float JSON values like iceberg-java (#3476)
    
    * fix(spec): read float JSON values like iceberg-java
    
    The Float branch of RawLiteral::try_into rejected a double whose
    distance to the narrowed f32 exceeded the absolute f32::EPSILON, so
    valid values such as 100.1 and 123.456 failed. iceberg-java narrows
    with floatValue() and does no precision check; do the same.
    
    * test(spec): pin non-finite and negative zero double to float
    
    Check NaN, both infinities, and the sign of zero when narrowing Avro 
doubles.
---
 crates/iceberg/src/spec/values/serde.rs | 12 +-----
 crates/iceberg/src/spec/values/tests.rs | 69 +++++++++++++++++++++++++++++++++
 2 files changed, 70 insertions(+), 11 deletions(-)

diff --git a/crates/iceberg/src/spec/values/serde.rs 
b/crates/iceberg/src/spec/values/serde.rs
index 7fa41159d..028c2d796 100644
--- a/crates/iceberg/src/spec/values/serde.rs
+++ b/crates/iceberg/src/spec/values/serde.rs
@@ -467,17 +467,7 @@ pub(crate) mod _serde {
                     _ => Err(invalid_err("float")),
                 },
                 RawLiteralEnum::Double(v) => match ty {
-                    Type::Primitive(PrimitiveType::Float) => {
-                        let v_32 = v as f32;
-                        if v_32.is_finite() {
-                            let v_64 = f64::from(v_32);
-                            if (v_64 - v).abs() > f32::EPSILON as f64 {
-                                // there is a precision loss
-                                return Err(invalid_err("double"));
-                            }
-                        }
-                        Ok(Some(Literal::float(v_32)))
-                    }
+                    Type::Primitive(PrimitiveType::Float) => 
Ok(Some(Literal::float(v as f32))),
                     Type::Primitive(PrimitiveType::Double) => 
Ok(Some(Literal::double(v))),
                     _ => Err(invalid_err("double")),
                 },
diff --git a/crates/iceberg/src/spec/values/tests.rs 
b/crates/iceberg/src/spec/values/tests.rs
index a6945015b..847fbd64c 100644
--- a/crates/iceberg/src/spec/values/tests.rs
+++ b/crates/iceberg/src/spec/values/tests.rs
@@ -187,6 +187,75 @@ fn json_double() {
     );
 }
 
+const FLOAT_VALUES: [f32; 6] = [
+    100.1,
+    123.456,
+    -123.456,
+    f32::MAX,
+    f32::MIN,
+    f32::from_bits(1),
+];
+
+#[test]
+fn raw_literal_json_number_to_float() {
+    for value in FLOAT_VALUES {
+        let raw: RawLiteral = 
serde_json::from_str(&format!("{value:e}")).unwrap();
+
+        assert_eq!(
+            raw.try_into(&Primitive(PrimitiveType::Float)).unwrap(),
+            Some(Literal::float(value))
+        );
+    }
+}
+
+#[test]
+fn raw_literal_avro_double_to_float() {
+    for value in FLOAT_VALUES {
+        for double in [f64::from(value), 
format!("{value:e}").parse().unwrap()] {
+            let raw: RawLiteral = 
apache_avro::from_value(&Value::Double(double)).unwrap();
+
+            assert_eq!(
+                raw.try_into(&Primitive(PrimitiveType::Float)).unwrap(),
+                Some(Literal::float(value))
+            );
+        }
+    }
+}
+
+#[test]
+fn raw_literal_double_without_exact_float_narrows_to_nearest() {
+    for (double, expected) in [(16777217.0_f64, 16777216.0_f32), 
(1.0000000001, 1.0)] {
+        let raw: RawLiteral = 
serde_json::from_str(&format!("{double:?}")).unwrap();
+
+        assert_eq!(
+            raw.try_into(&Primitive(PrimitiveType::Float)).unwrap(),
+            Some(Literal::float(expected))
+        );
+    }
+}
+
+#[test]
+fn raw_literal_avro_non_finite_and_negative_zero_double_to_float() {
+    for (double, expected) in [
+        (f64::NAN, f32::NAN),
+        (f64::INFINITY, f32::INFINITY),
+        (f64::NEG_INFINITY, f32::NEG_INFINITY),
+        (-0.0, -0.0_f32),
+    ] {
+        let raw: RawLiteral = 
apache_avro::from_value(&Value::Double(double)).unwrap();
+        let value = match 
raw.try_into(&Primitive(PrimitiveType::Float)).unwrap() {
+            Some(Literal::Primitive(PrimitiveLiteral::Float(value))) => 
value.0,
+            _ => panic!("unexpected literal type"),
+        };
+
+        if expected.is_nan() {
+            assert!(value.is_nan());
+        } else {
+            assert_eq!(value.to_bits(), expected.to_bits());
+        }
+    }
+}
+
 #[test]
 fn json_date() {
     let record = r#""2017-11-16""#;

Reply via email to