diff --git a/crates/iceberg/src/spec/values/datum.rs b/crates/iceberg/src/spec/values/datum.rs index 68ea6b3d46..383a8a8c6b 100644 --- a/crates/iceberg/src/spec/values/datum.rs +++ b/crates/iceberg/src/spec/values/datum.rs @@ -48,6 +48,8 @@ pub(crate) const INT_MAX: i32 = 2147483647; pub(crate) const INT_MIN: i32 = -2147483648; pub(crate) const LONG_MAX: i64 = 9223372036854775807; pub(crate) const LONG_MIN: i64 = -9223372036854775808; +pub(crate) const FLOAT_MAX: f32 = 3.4028235e38; +pub(crate) const FLOAT_MIN: f32 = -3.4028235e38; /// Literal associated with its type. The value and type pair is checked when construction, so the type and value is /// guaranteed to be correct when used. @@ -1109,6 +1111,16 @@ impl Datum { }) } + fn double_to_float + PartialOrd>(val: T) -> Datum { + if val > FLOAT_MAX as f64 { + Datum::new(PrimitiveType::Float, PrimitiveLiteral::AboveMax) + } else if val < FLOAT_MIN as f64 { + Datum::new(PrimitiveType::Float, PrimitiveLiteral::BelowMin) + } else { + Datum::float(val.into() as f32) + } + } + /// Convert the datum to `target_type`. pub fn to(self, target_type: &Type) -> Result { match target_type { @@ -1146,6 +1158,9 @@ impl Datum { (PrimitiveLiteral::String(val), _, PrimitiveType::Timestamptz) => { Datum::timestamptz_from_str(val) } + (PrimitiveLiteral::Double(val), _, PrimitiveType::Float) => { + Ok(Datum::double_to_float(**val)) + } // TODO: implement more type conversions (_, self_type, target_type) if self_type == target_type => Ok(self), diff --git a/crates/iceberg/src/spec/values/tests.rs b/crates/iceberg/src/spec/values/tests.rs index 41238ed899..8477802c9e 100644 --- a/crates/iceberg/src/spec/values/tests.rs +++ b/crates/iceberg/src/spec/values/tests.rs @@ -1352,3 +1352,39 @@ fn test_date_from_json_as_number() { // Both formats should produce the same Literal value } + +#[test] +fn test_iceberg_double_convert_to_float() { + let float_below_min = Datum::new(PrimitiveType::Float, PrimitiveLiteral::BelowMin); + let float_above_max = Datum::new(PrimitiveType::Float, PrimitiveLiteral::AboveMax); + + let test_data = [ + (Datum::double(-f64::NAN), Datum::float(-f32::NAN)), + (Datum::double(-f64::INFINITY), float_below_min.clone()), + (Datum::double(f64::MIN), float_below_min), + (Datum::double(f32::MIN as f64), Datum::float(f32::MIN)), + (Datum::double(-1.0), Datum::float(-1.0)), + (Datum::double(-f64::MIN_POSITIVE), Datum::float(-0.0)), + ( + Datum::double(-f32::MIN_POSITIVE as f64), + Datum::float(-f32::MIN_POSITIVE), + ), + (Datum::double(-0.0), Datum::float(-0.0)), + (Datum::double(0.0), Datum::float(0.0)), + (Datum::double(f64::MIN_POSITIVE), Datum::float(0.0)), + ( + Datum::double(f32::MIN_POSITIVE as f64), + Datum::float(f32::MIN_POSITIVE), + ), + (Datum::double(1.0), Datum::float(1.0)), + (Datum::double(f32::MAX as f64), Datum::float(f32::MAX)), + (Datum::double(f64::MAX), float_above_max.clone()), + (Datum::double(f64::INFINITY), float_above_max), + (Datum::double(f64::NAN), Datum::float(f32::NAN)), + ]; + + for (datum, expected) in test_data { + let result = datum.to(&Primitive(PrimitiveType::Float)).unwrap(); + assert_eq!(result, expected); + } +}