This is an automated email from the ASF dual-hosted git repository.
github-merge-queue[bot] 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 217e66dbd fix(spec): read float JSON values like iceberg-java (#3476)
217e66dbd is described below
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""#;