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

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


The following commit(s) were added to refs/heads/main by this push:
     new 22a766d  refactor(req): model items as totally ordered values (#244)
22a766d is described below

commit 22a766d20e46b704e669d210af174791c25be3a2
Author: tison <[email protected]>
AuthorDate: Sat Aug 29 13:39:09 2026 +0800

    refactor(req): model items as totally ordered values (#244)
---
 CHANGELOG.md                                       |   2 +-
 datasketches/src/req/compactor.rs                  |  85 ++++---
 datasketches/src/req/iter.rs                       |   7 +-
 datasketches/src/req/mod.rs                        |  43 +++-
 datasketches/src/req/sketch.rs                     |  94 +++++---
 datasketches/src/req/sorted_view.rs                |  73 +++---
 datasketches/src/req/value.rs                      | 246 +++++++++------------
 tests-integration/tests/req_test/accuracy.rs       |   7 +-
 tests-integration/tests/req_test/bounds.rs         |  31 +--
 tests-integration/tests/req_test/core.rs           | 134 ++++++-----
 .../tests/req_test/{main.rs => generic.rs}         |  28 ++-
 tests-integration/tests/req_test/main.rs           |  14 ++
 tests-integration/tests/req_test/merge.rs          |  57 ++---
 tests-integration/tests/req_test/property.rs       |  29 +--
 tests-integration/tests/req_test/query.rs          |  71 +++---
 .../tests/req_test/sorted_view_api.rs              |  76 ++-----
 tests-integration/tests/req_test/structure.rs      |  19 +-
 tests-integration/tests/serde_tests/req.rs         |  96 ++++----
 18 files changed, 567 insertions(+), 545 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 02ffab6..67d8965 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,7 +13,7 @@ All significant changes to this project will be documented in 
this file.
 
 ### New features
 
-* Add Relative Error Quantiles (REQ) sketches behind the `req` feature, 
including configurable high- or low-rank accuracy, rank, quantile, PMF, and CDF 
queries, merging, and C++/Java-compatible serialization.
+* Add Relative Error Quantiles (REQ) sketches behind the `req` feature, 
including configurable high- or low-rank accuracy, rank, quantile, PMF, and CDF 
queries, merging, totally ordered custom item types, the `ReqFloat` adapter for 
non-NaN floating-point values, and C++/Java-compatible serialization.
 
 ### Performance improvements
 
diff --git a/datasketches/src/req/compactor.rs 
b/datasketches/src/req/compactor.rs
index 07a6f38..e4835dc 100644
--- a/datasketches/src/req/compactor.rs
+++ b/datasketches/src/req/compactor.rs
@@ -28,21 +28,6 @@ use crate::req::nearest_even_section_size;
 use crate::req::serialization::validate_compactor_state;
 use crate::req::value::ReqValue;
 
-fn normalized_sort_state<T: ReqValue>(items: &[T], claimed_sorted: bool) -> 
Result<bool, Error> {
-    let mut previous: Option<&T> = None;
-    let mut sorted = claimed_sorted;
-    for item in items {
-        if item.is_nan() {
-            return Err(Error::deserial("REQ compactor contains a NaN item"));
-        }
-        if sorted && previous.is_some_and(|previous| 
previous.compare(item).is_gt()) {
-            sorted = false;
-        }
-        previous = Some(item);
-    }
-    Ok(sorted)
-}
-
 /// A compactor maintains items at a specific level of the REQ sketch.
 ///
 /// When the compactor reaches its nominal capacity, it performs compaction
@@ -75,7 +60,7 @@ pub(super) struct Compactor<T> {
 
 impl<T> Compactor<T>
 where
-    T: Clone + ReqValue,
+    T: Clone + Ord,
 {
     /// Creates a new compactor for the given level.
     ///
@@ -140,7 +125,7 @@ where
                 self.merge_sorted(&other.items);
             } else {
                 let mut other_items = other.items.clone();
-                other_items.sort_unstable_by(|a, b| a.compare(b));
+                other_items.sort_unstable();
                 self.merge_sorted(&other_items);
             }
         }
@@ -158,17 +143,14 @@ where
     pub(super) fn count_below(&self, item: &T, inclusive: bool) -> usize {
         if self.is_sorted {
             if inclusive {
-                self.items.partition_point(|x| x.compare(item).is_le())
+                self.items.partition_point(|x| x <= item)
             } else {
-                self.items.partition_point(|x| x.compare(item).is_lt())
+                self.items.partition_point(|x| x < item)
             }
         } else {
             self.items
                 .iter()
-                .filter(|x| {
-                    let ord = x.compare(item);
-                    if inclusive { ord.is_le() } else { ord.is_lt() }
-                })
+                .filter(|x| if inclusive { *x <= item } else { *x < item })
                 .count()
         }
     }
@@ -201,7 +183,7 @@ where
 
         // Two-pointer merge into scratch buffer
         while i < a.len() && j < b.len() {
-            if a[i].compare(&b[j]).is_le() {
+            if a[i] <= b[j] {
                 self.scratch_buffer.push(a[i].clone());
                 i += 1;
             } else {
@@ -229,7 +211,7 @@ where
     pub(super) fn sort(&mut self) {
         if !self.is_sorted {
             // Use unstable sort for better performance (stable not needed for 
REQ sketch)
-            self.items.sort_unstable_by(|a, b| a.compare(b));
+            self.items.sort_unstable();
             self.is_sorted = true;
         }
     }
@@ -367,7 +349,10 @@ where
     }
 
     /// Serialize this compactor (preamble + items) into the byte buffer.
-    pub(super) fn serialize_into(&self, bytes: &mut crate::codec::SketchBytes) 
{
+    pub(super) fn serialize_into(&self, bytes: &mut crate::codec::SketchBytes)
+    where
+        T: ReqValue,
+    {
         bytes.write_u64_le(self.state);
         bytes.write_f32_le(self.section_size_raw);
         bytes.write_u8(self.lg_weight);
@@ -386,7 +371,10 @@ where
         expected_lg_weight: u8,
         rank_accuracy: RankAccuracy,
         sorted: bool,
-    ) -> Result<Self, crate::error::Error> {
+    ) -> Result<Self, Error>
+    where
+        T: ReqValue,
+    {
         use crate::codec::assert::insufficient_data;
         let state = cursor
             .read_u64_le()
@@ -425,7 +413,7 @@ where
         for _ in 0..num_items {
             items.push(T::deserialize_value(cursor)?);
         }
-        let sorted = normalized_sort_state(&items, sorted)?;
+        let sorted = sorted && items.is_sorted();
 
         Ok(Compactor::from_serialized_state(
             lg_weight,
@@ -442,23 +430,19 @@ where
     ///
     /// The wire format omits the compactor preamble for tiny sketches (n ≤ 
4); this
     /// helper synthesises a fresh compactor and seeds it with the 
deserialized items.
-    /// `is_sorted` is taken verbatim from the wire — both C++ and Java 
produce valid
-    /// sketches but disagree on this flag for n=1 (C++ sets true, Java sets 
false),
-    /// so faithful round-trip requires preserving whatever the input said.
+    /// A false wire flag stays false for byte-stable C++/Java round trips; a 
true flag
+    /// is cleared if the items are not actually sorted.
     pub(super) fn raw_items_compactor(
         k: u16,
         rank_accuracy: RankAccuracy,
         items: Vec<T>,
         is_sorted: bool,
-    ) -> Result<Self, Error> {
-        let is_sorted = normalized_sort_state(&items, is_sorted)?;
+    ) -> Self {
+        let is_sorted = is_sorted && items.is_sorted();
         let mut c = Self::new(0, k, rank_accuracy);
-        for item in items {
-            c.append(item);
-        }
-        // append() may have flipped is_sorted off; restore the wire flag 
verbatim.
+        c.items = items;
         c.is_sorted = is_sorted;
-        Ok(c)
+        c
     }
 
     /// Reconstruct a Compactor from deserialized state.
@@ -494,7 +478,7 @@ where
 #[cfg(test)]
 impl<T> Compactor<T>
 where
-    T: Clone + ReqValue,
+    T: Clone + Ord,
 {
     /// Returns the level (log weight) of this compactor. Test-only accessor.
     pub(super) fn lg_weight(&self) -> u8 {
@@ -513,6 +497,7 @@ mod tests {
     use googletest::prelude::ge;
 
     use super::*;
+    use crate::req::ReqFloat;
 
     #[test]
     fn test_new_compactor() {
@@ -576,9 +561,9 @@ mod tests {
         use crate::codec::SketchBytes;
         use crate::codec::SketchSlice;
 
-        let mut c: Compactor<f32> = Compactor::new(0, 12, 
RankAccuracy::HighRank);
+        let mut c: Compactor<ReqFloat<f32>> = Compactor::new(0, 12, 
RankAccuracy::HighRank);
         for i in 0..30 {
-            c.append(i as f32);
+            c.append(ReqFloat::<f32>::new(i as f32).unwrap());
         }
         c.sort();
 
@@ -587,14 +572,20 @@ mod tests {
         let raw = bytes.into_bytes();
 
         let mut cursor = SketchSlice::new(&raw);
-        let c2 = Compactor::<f32>::deserialize(&mut cursor, 12, 0, 
RankAccuracy::HighRank, true)
-            .unwrap();
+        let c2 = Compactor::<ReqFloat<f32>>::deserialize(
+            &mut cursor,
+            12,
+            0,
+            RankAccuracy::HighRank,
+            true,
+        )
+        .unwrap();
 
         assert_eq!(c.num_items(), c2.num_items());
         assert_eq!(c.lg_weight(), c2.lg_weight());
         assert_eq!(c.state(), c2.state());
-        let xs: Vec<f32> = c.iter().copied().collect();
-        let ys: Vec<f32> = c2.iter().copied().collect();
+        let xs: Vec<ReqFloat<f32>> = c.iter().copied().collect();
+        let ys: Vec<ReqFloat<f32>> = c2.iter().copied().collect();
         assert_eq!(xs, ys);
     }
 
@@ -615,8 +606,8 @@ mod tests {
         //   - state=0xFFFF >= (1<<11)=2048 ✓ → num_sections=24, ssr≈4.24
         //   - state=0xFFFF >= (1<<23)=8388608 ✗ → stop
         // Expected: num_sections == 24 with the fix; == 6 with only one call.
-        let mut a: Compactor<f64> = Compactor::new(0, 12, 
RankAccuracy::HighRank);
-        let mut b: Compactor<f64> = Compactor::new(0, 12, 
RankAccuracy::HighRank);
+        let mut a: Compactor<i32> = Compactor::new(0, 12, 
RankAccuracy::HighRank);
+        let mut b: Compactor<i32> = Compactor::new(0, 12, 
RankAccuracy::HighRank);
         b.state = 0xFFFF;
 
         assert_eq!(a.num_sections, 3, "default num_sections sanity");
diff --git a/datasketches/src/req/iter.rs b/datasketches/src/req/iter.rs
index a35aadb..ba5c0f3 100644
--- a/datasketches/src/req/iter.rs
+++ b/datasketches/src/req/iter.rs
@@ -18,7 +18,6 @@
 //! Iterator implementations for REQ sketch inspection.
 
 use crate::req::compactor::Compactor;
-use crate::req::value::ReqValue;
 
 /// Iterator over (item, weight) pairs in a REQ sketch.
 ///
@@ -26,14 +25,14 @@ use crate::req::value::ReqValue;
 /// which depend on the level of the compactor they're stored in.
 ///
 /// Zero-allocation implementation that works directly with slices.
-pub struct ReqSketchIterator<'a, T: ReqValue> {
+pub struct ReqSketchIterator<'a, T> {
     compactors: &'a [Compactor<T>],
     current_level: usize,
     current_level_iter: Option<std::slice::Iter<'a, T>>,
     current_weight: u64,
 }
 
-impl<'a, T: ReqValue> ReqSketchIterator<'a, T> {
+impl<'a, T: Clone + Ord> ReqSketchIterator<'a, T> {
     /// Creates a new iterator over the compactors.
     pub(super) fn new(compactors: &'a [Compactor<T>]) -> Self {
         let mut iter = Self {
@@ -65,7 +64,7 @@ impl<'a, T: ReqValue> ReqSketchIterator<'a, T> {
     }
 }
 
-impl<T: ReqValue> Iterator for ReqSketchIterator<'_, T> {
+impl<T: Clone + Ord> Iterator for ReqSketchIterator<'_, T> {
     type Item = (T, u64);
 
     fn next(&mut self) -> Option<Self::Item> {
diff --git a/datasketches/src/req/mod.rs b/datasketches/src/req/mod.rs
index 37f26ca..8714f5e 100644
--- a/datasketches/src/req/mod.rs
+++ b/datasketches/src/req/mod.rs
@@ -17,11 +17,37 @@
 
 //! Relative Error Quantiles (REQ) sketch.
 //!
-//! Provides approximate quantile estimation with relative error guarantees, 
especially
-//! useful for streaming scenarios needing bounded memory. Based on the paper
-//! [Relative Error Streaming Quantiles](https://arxiv.org/abs/2004.01668) by 
Cormode,
-//! Karnin, Liberty, Thaler and Veselý, and on the Apache DataSketches C++ 
reference
-//! implementation.
+//! [`ReqSketch`] provides bounded-memory rank, quantile, PMF, and CDF 
estimates with
+//! configurable relative accuracy at either end of the rank domain. It is 
based on
+//! [Relative Error Streaming Quantiles](https://arxiv.org/abs/2004.01668) and 
the Apache
+//! DataSketches C++ implementation.
+//!
+//! # Item ordering
+//!
+//! The REQ paper defines input items as coming from a totally ordered 
universe. Accordingly,
+//! sketch items must implement [`Ord`]. Rust's `f32` and `f64` do not 
implement `Ord` because NaN
+//! is unordered; wrap floating-point items in [`ReqFloat`], whose constructor 
rejects NaN. Signed
+//! zeros compare equal and infinities retain their usual numerical order.
+//!
+//! Custom item types need only [`Clone`] and [`Ord`] for in-memory use. 
Serialization additionally
+//! requires [`ReqValue`].
+//!
+//! # Example
+//!
+//! ```
+//! use datasketches::req::ReqFloat;
+//! use datasketches::req::ReqSketch;
+//! use datasketches::req::SearchCriteria;
+//!
+//! let mut sketch = ReqSketch::default();
+//! for value in [1.0, 2.0, 3.0] {
+//!     sketch.update(ReqFloat::<f64>::new(value)?);
+//! }
+//!
+//! let median = sketch.quantile(0.5, SearchCriteria::Inclusive)?;
+//! assert_eq!(median.into_inner(), 2.0);
+//! # Ok::<(), datasketches::error::Error>(())
+//! ```
 
 mod compactor;
 mod iter;
@@ -33,6 +59,7 @@ mod value;
 pub use self::iter::ReqSketchIterator;
 pub use self::sketch::ReqSketch;
 pub use self::sorted_view::SortedView;
+pub use self::value::ReqFloat;
 pub use self::value::ReqValue;
 
 /// Default value of `k` if not specified. Roughly 1% relative error at 95% 
confidence.
@@ -52,13 +79,13 @@ pub enum RankAccuracy {
     LowRank,
 }
 
-/// Whether queries include the weight of the search item.
+/// Selects the rank definition used by rank, quantile, PMF, and CDF queries.
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
 pub enum SearchCriteria {
-    /// Include the weight of the search item in the result.
+    /// Define rank as the fraction of values less than or equal to the 
boundary.
     #[default]
     Inclusive,
-    /// Exclude the weight of the search item from the result.
+    /// Define rank as the fraction of values strictly less than the boundary.
     Exclusive,
 }
 
diff --git a/datasketches/src/req/sketch.rs b/datasketches/src/req/sketch.rs
index 16fe895..1a2e263 100644
--- a/datasketches/src/req/sketch.rs
+++ b/datasketches/src/req/sketch.rs
@@ -15,7 +15,7 @@
 // specific language governing permissions and limitations
 // under the License.
 
-//! REQ sketch — generic over `T: ReqValue`.
+//! Generic REQ sketch implementation.
 
 use crate::codec::SketchBytes;
 use crate::codec::SketchSlice;
@@ -45,9 +45,9 @@ use crate::req::value::ReqValue;
 
 /// A Relative Error Quantiles sketch for approximate quantile estimation.
 ///
-/// See the [module-level documentation](super) for background.
+/// See the [module level documentation](super) for item ordering and 
floating-point requirements.
 #[derive(Debug, Clone)]
-pub struct ReqSketch<T: ReqValue> {
+pub struct ReqSketch<T> {
     k: u16,
     rank_accuracy: RankAccuracy,
     n: u64,
@@ -59,14 +59,22 @@ pub struct ReqSketch<T: ReqValue> {
     max_item: Option<T>,
 }
 
-impl<T: ReqValue> Default for ReqSketch<T> {
+impl<T> Default for ReqSketch<T>
+where
+    T: Clone + Ord,
+{
     fn default() -> Self {
         Self::make(DEFAULT_K, RankAccuracy::HighRank)
     }
 }
 
-impl<T: ReqValue> ReqSketch<T> {
-    /// Creates a new sketch with the given `k` and rank accuracy.
+impl<T> ReqSketch<T>
+where
+    T: Clone + Ord,
+{
+    /// Creates a sketch with the given `k` and rank-accuracy mode.
+    ///
+    /// Larger `k` improves accuracy at the cost of retained memory.
     ///
     /// # Errors
     ///
@@ -93,7 +101,7 @@ impl<T: ReqValue> ReqSketch<T> {
         self.rank_accuracy
     }
 
-    /// Returns the total number of items observed (matches C++ `get_n`).
+    /// Returns the total number of items observed.
     pub fn n(&self) -> u64 {
         self.n
     }
@@ -124,21 +132,15 @@ impl<T: ReqValue> ReqSketch<T> {
     }
 
     /// Updates the sketch with a new item.
-    ///
-    /// NaN inputs are silently ignored for floating-point types, matching the 
behavior
-    /// of the Java reference implementation (`checkNaNUpdate`). This is 
intentional.
     pub fn update(&mut self, item: T) {
-        if item.is_nan() {
-            return;
-        }
         match &mut self.min_item {
             None => self.min_item = Some(item.clone()),
-            Some(cur) if item.compare(cur).is_lt() => *cur = item.clone(),
+            Some(cur) if item.cmp(cur).is_lt() => *cur = item.clone(),
             _ => {}
         }
         match &mut self.max_item {
             None => self.max_item = Some(item.clone()),
-            Some(cur) if item.compare(cur).is_gt() => *cur = item.clone(),
+            Some(cur) if item.cmp(cur).is_gt() => *cur = item.clone(),
             _ => {}
         }
 
@@ -174,14 +176,12 @@ impl<T: ReqValue> ReqSketch<T> {
     /// [`SortedView::rank`] on [`Self::sorted_view`].
     ///
     /// # Errors
-    /// Returns an error if the sketch is empty or `item` is NaN.
+    ///
+    /// Returns an error if the sketch is empty.
     pub fn rank(&self, item: &T, criteria: SearchCriteria) -> Result<f64, 
Error> {
         if self.is_empty() {
             return Err(Error::invalid_argument("sketch is empty"));
         }
-        if item.is_nan() {
-            return Err(Error::invalid_argument("query item is NaN"));
-        }
         let inclusive = matches!(criteria, SearchCriteria::Inclusive);
         let weight: u64 = self
             .compactors
@@ -195,6 +195,10 @@ impl<T: ReqValue> ReqSketch<T> {
     ///
     /// Builds a transient [`SortedView`] internally. For repeated quantile
     /// queries, take one snapshot with [`Self::sorted_view`] and query it.
+    ///
+    /// # Errors
+    ///
+    /// Returns an error if the sketch is empty or `rank` is outside `[0.0, 
1.0]`.
     pub fn quantile(&self, rank: f64, criteria: SearchCriteria) -> Result<T, 
Error> {
         if self.is_empty() {
             return Err(Error::invalid_argument("sketch is empty"));
@@ -210,6 +214,10 @@ impl<T: ReqValue> ReqSketch<T> {
     /// Returns approximate quantiles for the given normalized ranks.
     ///
     /// The sorted view is built once and shared across all ranks.
+    ///
+    /// # Errors
+    ///
+    /// Returns an error if the sketch is empty or any rank is outside `[0.0, 
1.0]`.
     pub fn quantiles(&self, ranks: &[f64], criteria: SearchCriteria) -> 
Result<Vec<T>, Error> {
         if self.is_empty() {
             return Err(Error::invalid_argument("sketch is empty"));
@@ -227,6 +235,10 @@ impl<T: ReqValue> ReqSketch<T> {
     }
 
     /// Returns the Probability Mass Function over the given split points.
+    ///
+    /// # Errors
+    ///
+    /// Returns an error if the sketch is empty or the split points are not 
strictly increasing.
     pub fn pmf(&self, split_points: &[T], criteria: SearchCriteria) -> 
Result<Vec<f64>, Error> {
         if self.is_empty() {
             return Err(Error::invalid_argument("sketch is empty"));
@@ -235,6 +247,10 @@ impl<T: ReqValue> ReqSketch<T> {
     }
 
     /// Returns the Cumulative Distribution Function over the given split 
points.
+    ///
+    /// # Errors
+    ///
+    /// Returns an error if the sketch is empty or the split points are not 
strictly increasing.
     pub fn cdf(&self, split_points: &[T], criteria: SearchCriteria) -> 
Result<Vec<f64>, Error> {
         if self.is_empty() {
             return Err(Error::invalid_argument("sketch is empty"));
@@ -273,19 +289,21 @@ impl<T: ReqValue> ReqSketch<T> {
     /// # Examples
     ///
     /// ```
+    /// use datasketches::req::ReqFloat;
     /// use datasketches::req::ReqSketch;
     ///
     /// let mut first = ReqSketch::default();
-    /// first.update(1.0_f64);
+    /// first.update(ReqFloat::<f64>::new(1.0)?);
     ///
     /// let mut second = ReqSketch::default();
-    /// second.update(2.0_f64);
+    /// second.update(ReqFloat::<f64>::new(2.0)?);
     ///
     /// let mut combined = ReqSketch::default();
     /// combined.merge(&first).unwrap();
     /// combined.merge(&second).unwrap();
     ///
     /// assert_eq!(combined.n(), 2);
+    /// # Ok::<(), datasketches::error::Error>(())
     /// ```
     pub fn merge(&mut self, other: &Self) -> Result<(), Error> {
         if self.rank_accuracy != other.rank_accuracy {
@@ -303,14 +321,14 @@ impl<T: ReqValue> ReqSketch<T> {
         if let Some(m) = &other.min_item {
             match &self.min_item {
                 None => self.min_item = Some(m.clone()),
-                Some(cur) if m.compare(cur).is_lt() => self.min_item = 
Some(m.clone()),
+                Some(cur) if m.cmp(cur).is_lt() => self.min_item = 
Some(m.clone()),
                 _ => {}
             }
         }
         if let Some(m) = &other.max_item {
             match &self.max_item {
                 None => self.max_item = Some(m.clone()),
-                Some(cur) if m.compare(cur).is_gt() => self.max_item = 
Some(m.clone()),
+                Some(cur) if m.cmp(cur).is_gt() => self.max_item = 
Some(m.clone()),
                 _ => {}
             }
         }
@@ -473,8 +491,11 @@ impl<T: ReqValue> ReqSketch<T> {
         self.n <= RAW_ITEMS_THRESHOLD && self.compactors.len() == 1
     }
 
-    /// Number of bytes required to serialize the sketch.
-    pub fn serialized_size_bytes(&self) -> usize {
+    /// Returns the number of bytes required to serialize the sketch.
+    pub fn serialized_size_bytes(&self) -> usize
+    where
+        T: ReqValue,
+    {
         // Fixed sketch preamble: 8 bytes (preamble_ints, serial_version, 
family,
         // flags, k(2), num_levels, num_raw_items).
         let mut size = 8usize;
@@ -502,8 +523,11 @@ impl<T: ReqValue> ReqSketch<T> {
         size
     }
 
-    /// Serialize the sketch into a `Vec<u8>` matching the C++/Java REQ wire 
format.
-    pub fn serialize(&self) -> Vec<u8> {
+    /// Serializes the sketch using the C++/Java-compatible REQ wire format.
+    pub fn serialize(&self) -> Vec<u8>
+    where
+        T: ReqValue,
+    {
         let mut out = SketchBytes::with_capacity(self.serialized_size_bytes());
         let preamble_ints = if self.is_estimation_mode() {
             PREAMBLE_INTS_ESTIMATION
@@ -554,7 +578,10 @@ impl<T: ReqValue> ReqSketch<T> {
     ///
     /// Returns an error if the input is truncated or contains an inconsistent
     /// REQ serialized state.
-    pub fn deserialize(bytes: &[u8]) -> Result<Self, Error> {
+    pub fn deserialize(bytes: &[u8]) -> Result<Self, Error>
+    where
+        T: ReqValue,
+    {
         let mut cursor = SketchSlice::new(bytes);
         let preamble_ints = cursor
             .read_u8()
@@ -646,10 +673,7 @@ impl<T: ReqValue> ReqSketch<T> {
             max_item = Some(T::deserialize_value(&mut cursor)?);
             let min = min_item.as_ref().unwrap();
             let max = max_item.as_ref().unwrap();
-            if min.is_nan() || max.is_nan() {
-                return Err(Error::deserial("REQ sketch min or max item is 
NaN"));
-            }
-            if min.compare(max).is_gt() {
+            if min > max {
                 return Err(Error::deserial(
                     "REQ sketch min item is greater than max item",
                 ));
@@ -665,7 +689,7 @@ impl<T: ReqValue> ReqSketch<T> {
                 items.push(T::deserialize_value(&mut cursor)?);
             }
             let c =
-                Compactor::<T>::raw_items_compactor(k, rank_accuracy, items, 
is_level_zero_sorted)?;
+                Compactor::<T>::raw_items_compactor(k, rank_accuracy, items, 
is_level_zero_sorted);
             compactors.push(c);
         } else {
             for i in 0..num_levels {
@@ -685,10 +709,10 @@ impl<T: ReqValue> ReqSketch<T> {
                 let mut mn = first.clone();
                 let mut mx = first.clone();
                 for x in iter {
-                    if x.compare(&mn).is_lt() {
+                    if x < &mn {
                         mn = x.clone();
                     }
-                    if x.compare(&mx).is_gt() {
+                    if x > &mx {
                         mx = x.clone();
                     }
                 }
diff --git a/datasketches/src/req/sorted_view.rs 
b/datasketches/src/req/sorted_view.rs
index 5c2d5a1..d1313ac 100644
--- a/datasketches/src/req/sorted_view.rs
+++ b/datasketches/src/req/sorted_view.rs
@@ -19,10 +19,8 @@
 
 use crate::error::Error;
 use crate::req::SearchCriteria;
-use crate::req::value::ReqValue;
 
-/// An owned, sorted snapshot of a [`ReqSketch`](crate::req::ReqSketch)'s 
items with
-/// their cumulative weights.
+/// An owned, sorted snapshot of a [`ReqSketch`](crate::req::ReqSketch).
 ///
 /// Obtain one with 
[`ReqSketch::sorted_view`](crate::req::ReqSketch::sorted_view).
 /// The view is independent of the sketch: it can be queried (and sent to other
@@ -42,7 +40,7 @@ pub struct SortedView<T> {
 
 impl<T> SortedView<T>
 where
-    T: ReqValue,
+    T: Clone + Ord,
 {
     /// Creates a new sorted view from weighted items.
     ///
@@ -60,7 +58,7 @@ where
         }
 
         // Sort by item value - use unstable sort for better performance
-        weighted_items.sort_unstable_by(|a, b| a.0.compare(&b.0));
+        weighted_items.sort_unstable_by(|a, b| a.0.cmp(&b.0));
 
         let mut items: Vec<T> = Vec::with_capacity(weighted_items.len());
         let mut cumulative_weights = Vec::with_capacity(weighted_items.len());
@@ -68,7 +66,7 @@ where
 
         for (item, weight) in weighted_items {
             if let Some(last) = items.last() {
-                if matches!(last.compare(&item), std::cmp::Ordering::Equal) {
+                if last == &item {
                     cumulative_weight += weight;
                     let last_idx = cumulative_weights.len() - 1;
                     cumulative_weights[last_idx] = cumulative_weight;
@@ -102,27 +100,20 @@ where
         self.total_weight
     }
 
-    /// Returns the approximate normalized rank of the given item in `[0.0, 
1.0]`.
-    ///
-    /// # Arguments
-    /// * `item` - The item to find the rank for
-    /// * `criteria` - Whether to include the item's weight in the rank
+    /// Returns the approximate normalized rank of `item` in `[0.0, 1.0]`.
     ///
     /// # Errors
-    /// Returns an error if the view is empty or `item` is NaN.
+    ///
+    /// Returns an error if the view is empty.
     pub fn rank(&self, item: &T, criteria: SearchCriteria) -> Result<f64, 
Error> {
         if self.is_empty() {
             return Err(Error::invalid_argument("sketch is empty"));
         }
-        if item.is_nan() {
-            return Err(Error::invalid_argument("query item is NaN"));
-        }
-
         match criteria {
             SearchCriteria::Inclusive => {
                 // Find the last position where items[i] <= item
                 // partition_point finds first index where predicate is false
-                let pos = self.items.partition_point(|x| 
x.compare(item).is_le());
+                let pos = self.items.partition_point(|x| x <= item);
                 if pos == 0 {
                     Ok(0.0)
                 } else {
@@ -131,7 +122,7 @@ where
             }
             SearchCriteria::Exclusive => {
                 // Find the last position where items[i] < item
-                let pos = self.items.partition_point(|x| 
x.compare(item).is_lt());
+                let pos = self.items.partition_point(|x| x < item);
                 if pos == 0 {
                     Ok(0.0)
                 } else {
@@ -141,14 +132,11 @@ where
         }
     }
 
-    /// Returns the approximate quantile for the given normalized rank.
+    /// Returns the approximate quantile at the given normalized rank.
     ///
-    /// # Arguments
-    /// * `rank` - A normalized rank in [0.0, 1.0]
-    /// * `criteria` - Search criteria for quantile selection
+    /// # Errors
     ///
-    /// # Returns
-    /// The item at approximately the given rank
+    /// Returns an error if the view is empty or `rank` is outside `[0.0, 
1.0]`.
     pub fn quantile(&self, rank: f64, criteria: SearchCriteria) -> Result<T, 
Error> {
         if self.is_empty() {
             return Err(Error::invalid_argument("sketch is empty"));
@@ -199,14 +187,13 @@ where
         Ok(self.items[index].clone())
     }
 
-    /// Returns the Probability Mass Function (PMF) for the given split points.
+    /// Returns the probability mass function (PMF) over the given split 
points.
     ///
-    /// # Arguments
-    /// * `split_points` - Array of split points that divide the domain
-    /// * `criteria` - Search criteria for boundary handling
+    /// The result contains one more value than `split_points`.
     ///
-    /// # Returns
-    /// Array of probabilities for each interval defined by the split points
+    /// # Errors
+    ///
+    /// Returns an error if the view is empty or the split points are not 
strictly increasing.
     pub fn pmf(&self, split_points: &[T], criteria: SearchCriteria) -> 
Result<Vec<f64>, Error> {
         if self.is_empty() {
             return Err(Error::invalid_argument("sketch is empty"));
@@ -229,14 +216,13 @@ where
         Ok(result)
     }
 
-    /// Returns the Cumulative Distribution Function (CDF) for the given split 
points.
+    /// Returns the cumulative distribution function (CDF) over the given 
split points.
     ///
-    /// # Arguments
-    /// * `split_points` - Array of split points that divide the domain
-    /// * `criteria` - Search criteria for boundary handling
+    /// The result contains one more value than `split_points` and ends at 
`1.0`.
+    ///
+    /// # Errors
     ///
-    /// # Returns
-    /// Array of cumulative probabilities at each split point
+    /// Returns an error if the view is empty or the split points are not 
strictly increasing.
     pub fn cdf(&self, split_points: &[T], criteria: SearchCriteria) -> 
Result<Vec<f64>, Error> {
         if self.is_empty() {
             return Err(Error::invalid_argument("sketch is empty"));
@@ -256,18 +242,11 @@ where
         Ok(result)
     }
 
-    // Private helper methods
-
     fn validate_split_points(&self, split_points: &[T]) -> Result<(), Error> {
-        for (i, split_point) in split_points.iter().enumerate() {
-            if split_point.is_nan() {
-                return Err(Error::invalid_argument("Split points must not be 
NaN"));
-            }
-            if i > 0 && split_points[i - 1].compare(split_point).is_ge() {
-                return Err(Error::invalid_argument(
-                    "Split points must be unique and monotonically 
increasing".to_string(),
-                ));
-            }
+        if split_points.windows(2).any(|pair| pair[0] >= pair[1]) {
+            return Err(Error::invalid_argument(
+                "Split points must be unique and monotonically 
increasing".to_string(),
+            ));
         }
         Ok(())
     }
diff --git a/datasketches/src/req/value.rs b/datasketches/src/req/value.rs
index a2c5ed4..8bd3f30 100644
--- a/datasketches/src/req/value.rs
+++ b/datasketches/src/req/value.rs
@@ -15,49 +15,126 @@
 // specific language governing permissions and limitations
 // under the License.
 
-//! Trait for types storable in a [`ReqSketch`](crate::req::ReqSketch).
+//! REQ item types and serialization.
 
 use std::cmp::Ordering;
+use std::fmt;
 use std::mem::size_of;
+use std::ops::Deref;
 
 use crate::codec::SketchBytes;
 use crate::codec::SketchSlice;
 use crate::codec::assert::insufficient_data;
 use crate::error::Error;
 
-/// Trait for types that can be stored in a 
[`ReqSketch`](crate::req::ReqSketch).
+/// A non-NaN floating-point adapter for [`ReqSketch`](crate::req::ReqSketch).
 ///
-/// Provides ordering and binary serialization compatible with the Apache 
DataSketches
-/// REQ wire format used by the C++ and Java reference implementations.
-pub trait ReqValue: Sized + Clone + PartialOrd {
-    /// Compares two values. See each implementation for its ordering 
semantics.
-    fn compare(&self, other: &Self) -> Ordering;
+/// REQ requires a totally ordered item domain, while primitive floats are 
unordered in the
+/// presence of NaN. Construction therefore rejects NaN. Other values retain 
their numerical
+/// order: signed zeros compare equal and infinities are allowed.
+///
+/// The inner float is available through [`into_inner`](Self::into_inner) or 
immutable
+/// dereferencing.
+#[repr(transparent)]
+#[derive(Clone, Copy, PartialEq, PartialOrd)]
+pub struct ReqFloat<T>(T);
+
+impl<T> ReqFloat<T> {
+    /// Returns the wrapped floating-point value.
+    #[inline(always)]
+    pub fn into_inner(self) -> T {
+        self.0
+    }
+}
+
+impl<T> Deref for ReqFloat<T> {
+    type Target = T;
 
-    /// Returns true if this value is the floating-point NaN sentinel.
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl<T: fmt::Debug> fmt::Debug for ReqFloat<T> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        self.0.fmt(f)
+    }
+}
+
+impl<T: fmt::Display> fmt::Display for ReqFloat<T> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        self.0.fmt(f)
+    }
+}
+
+impl ReqFloat<f32> {
+    /// Creates a non-NaN value.
+    ///
+    /// # Errors
     ///
-    /// Default: false (integer types are never NaN). Float impls override
-    /// to delegate to [`f32::is_nan`] / [`f64::is_nan`].
+    /// Returns an error if `value` is NaN.
     #[inline(always)]
-    fn is_nan(&self) -> bool {
-        false
+    pub fn new(value: f32) -> Result<Self, Error> {
+        if value.is_nan() {
+            Err(Error::invalid_argument("REQ float must not be NaN"))
+        } else {
+            Ok(Self(value))
+        }
     }
+}
+
+impl Eq for ReqFloat<f32> {}
 
-    /// Number of bytes this value will occupy when serialized.
+impl Ord for ReqFloat<f32> {
+    #[inline(always)]
+    fn cmp(&self, other: &Self) -> Ordering {
+        self.0.partial_cmp(&other.0).unwrap()
+    }
+}
+
+impl ReqFloat<f64> {
+    /// Creates a non-NaN value.
+    ///
+    /// # Errors
+    ///
+    /// Returns an error if `value` is NaN.
+    #[inline(always)]
+    pub fn new(value: f64) -> Result<Self, Error> {
+        if value.is_nan() {
+            Err(Error::invalid_argument("REQ float must not be NaN"))
+        } else {
+            Ok(Self(value))
+        }
+    }
+}
+
+impl Eq for ReqFloat<f64> {}
+
+impl Ord for ReqFloat<f64> {
+    #[inline(always)]
+    fn cmp(&self, other: &Self) -> Ordering {
+        self.0.partial_cmp(&other.0).unwrap()
+    }
+}
+
+/// Binary serialization for REQ items.
+///
+/// This trait is not required for in-memory sketch operations. Implement it 
only when a custom
+/// item type must be used with 
[`ReqSketch::serialize`](crate::req::ReqSketch::serialize) and
+/// [`ReqSketch::deserialize`](crate::req::ReqSketch::deserialize). The 
encoded form must preserve
+/// the item's ordering across a round trip.
+pub trait ReqValue: Sized {
+    /// Returns the serialized size of `item` in bytes.
     fn serialize_size(item: &Self) -> usize;
 
-    /// Serialize this value into the byte buffer.
+    /// Serializes this value into `bytes`.
     fn serialize_value(&self, bytes: &mut SketchBytes);
 
-    /// Deserialize a value from the byte cursor.
+    /// Deserializes one value from `cursor`.
     fn deserialize_value(cursor: &mut SketchSlice<'_>) -> Result<Self, Error>;
 }
 
 impl ReqValue for i32 {
-    #[inline(always)]
-    fn compare(&self, other: &Self) -> Ordering {
-        self.cmp(other)
-    }
-
     fn serialize_size(_item: &Self) -> usize {
         size_of::<Self>()
     }
@@ -74,11 +151,6 @@ impl ReqValue for i32 {
 }
 
 impl ReqValue for i64 {
-    #[inline(always)]
-    fn compare(&self, other: &Self) -> Ordering {
-        self.cmp(other)
-    }
-
     fn serialize_size(_item: &Self) -> usize {
         size_of::<Self>()
     }
@@ -95,11 +167,6 @@ impl ReqValue for i64 {
 }
 
 impl ReqValue for u32 {
-    #[inline(always)]
-    fn compare(&self, other: &Self) -> Ordering {
-        self.cmp(other)
-    }
-
     fn serialize_size(_item: &Self) -> usize {
         size_of::<Self>()
     }
@@ -116,11 +183,6 @@ impl ReqValue for u32 {
 }
 
 impl ReqValue for u64 {
-    #[inline(always)]
-    fn compare(&self, other: &Self) -> Ordering {
-        self.cmp(other)
-    }
-
     fn serialize_size(_item: &Self) -> usize {
         size_of::<Self>()
     }
@@ -136,130 +198,36 @@ impl ReqValue for u64 {
     }
 }
 
-impl ReqValue for f32 {
-    #[inline(always)]
-    fn compare(&self, other: &Self) -> Ordering {
-        self.partial_cmp(other).unwrap()
-    }
-
-    #[inline(always)]
-    fn is_nan(&self) -> bool {
-        f32::is_nan(*self)
-    }
-
+impl ReqValue for ReqFloat<f32> {
     fn serialize_size(_item: &Self) -> usize {
         size_of::<Self>()
     }
 
     fn serialize_value(&self, bytes: &mut SketchBytes) {
-        bytes.write_f32_le(*self);
+        bytes.write_f32_le(self.0);
     }
 
     fn deserialize_value(cursor: &mut SketchSlice<'_>) -> Result<Self, Error> {
-        cursor
+        let value = cursor
             .read_f32_le()
-            .map_err(insufficient_data("failed to read f32 from REQ sketch"))
+            .map_err(insufficient_data("failed to read f32 from REQ sketch"))?;
+        Self::new(value).map_err(|_| Error::deserial("REQ float must not be 
NaN"))
     }
 }
 
-impl ReqValue for f64 {
-    #[inline(always)]
-    fn compare(&self, other: &Self) -> Ordering {
-        self.partial_cmp(other).unwrap()
-    }
-
-    #[inline(always)]
-    fn is_nan(&self) -> bool {
-        f64::is_nan(*self)
-    }
-
+impl ReqValue for ReqFloat<f64> {
     fn serialize_size(_item: &Self) -> usize {
         size_of::<Self>()
     }
 
     fn serialize_value(&self, bytes: &mut SketchBytes) {
-        bytes.write_f64_le(*self);
+        bytes.write_f64_le(self.0);
     }
 
     fn deserialize_value(cursor: &mut SketchSlice<'_>) -> Result<Self, Error> {
-        cursor
+        let value = cursor
             .read_f64_le()
-            .map_err(insufficient_data("failed to read f64 from REQ sketch"))
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    fn round_trip<T: ReqValue + PartialEq + std::fmt::Debug>(v: T) {
-        let mut bytes = SketchBytes::with_capacity(T::serialize_size(&v));
-        v.serialize_value(&mut bytes);
-        let raw = bytes.into_bytes();
-        assert_eq!(raw.len(), T::serialize_size(&v));
-        let mut cursor = SketchSlice::new(&raw);
-        let got = T::deserialize_value(&mut cursor).unwrap();
-        assert_eq!(got, v);
-    }
-
-    #[test]
-    fn round_trip_integers() {
-        round_trip(0_i32);
-        round_trip(i32::MIN);
-        round_trip(i32::MAX);
-        round_trip(0_i64);
-        round_trip(i64::MIN);
-        round_trip(i64::MAX);
-        round_trip(0_u32);
-        round_trip(u32::MAX);
-        round_trip(0_u64);
-        round_trip(u64::MAX);
-    }
-
-    #[test]
-    fn round_trip_floats() {
-        round_trip(0.0_f32);
-        round_trip(-1.5_f32);
-        round_trip(f32::MIN);
-        round_trip(f32::MAX);
-        round_trip(f32::INFINITY);
-        round_trip(f32::NEG_INFINITY);
-        round_trip(0.0_f64);
-        round_trip(-1.5_f64);
-        round_trip(f64::MIN);
-        round_trip(f64::MAX);
-        round_trip(f64::INFINITY);
-        round_trip(f64::NEG_INFINITY);
-    }
-
-    #[test]
-    fn compare_for_f32_uses_numeric_order() {
-        assert_eq!(<f32 as ReqValue>::compare(&-0.0, &0.0), Ordering::Equal);
-        assert_eq!(
-            <f32 as ReqValue>::compare(&f32::NEG_INFINITY, &f32::INFINITY),
-            Ordering::Less
-        );
-    }
-
-    #[test]
-    fn compare_for_f64_uses_numeric_order() {
-        assert_eq!(<f64 as ReqValue>::compare(&-0.0, &0.0), Ordering::Equal);
-        assert_eq!(
-            <f64 as ReqValue>::compare(&f64::NEG_INFINITY, &f64::INFINITY),
-            Ordering::Less
-        );
-    }
-
-    #[test]
-    #[should_panic]
-    fn compare_for_floats_rejects_nan() {
-        <f64 as ReqValue>::compare(&f64::NAN, &0.0);
-    }
-
-    #[test]
-    fn compare_for_integers_matches_ord() {
-        assert_eq!(<i64 as ReqValue>::compare(&3, &5), Ordering::Less);
-        assert_eq!(<i64 as ReqValue>::compare(&5, &5), Ordering::Equal);
-        assert_eq!(<i64 as ReqValue>::compare(&7, &5), Ordering::Greater);
+            .map_err(insufficient_data("failed to read f64 from REQ sketch"))?;
+        Self::new(value).map_err(|_| Error::deserial("REQ float must not be 
NaN"))
     }
 }
diff --git a/tests-integration/tests/req_test/accuracy.rs 
b/tests-integration/tests/req_test/accuracy.rs
index 8ca9fde..d68f8c2 100644
--- a/tests-integration/tests/req_test/accuracy.rs
+++ b/tests-integration/tests/req_test/accuracy.rs
@@ -23,13 +23,16 @@ use datasketches::req::SearchCriteria;
 use googletest::assert_that;
 use googletest::prelude::le;
 
+use super::ReqF64;
+use super::req_f64;
+
 #[test]
 fn rank_space_error_is_bounded() -> Result<(), Error> {
-    let mut sketch = ReqSketch::default();
+    let mut sketch: ReqSketch<ReqF64> = ReqSketch::default();
     let n = 50_000;
 
     for i in 0..n {
-        sketch.update(i as f64);
+        sketch.update(req_f64(i as f64));
     }
 
     assert_eq!(sketch.n(), n as u64);
diff --git a/tests-integration/tests/req_test/bounds.rs 
b/tests-integration/tests/req_test/bounds.rs
index 13899af..b89b743 100644
--- a/tests-integration/tests/req_test/bounds.rs
+++ b/tests-integration/tests/req_test/bounds.rs
@@ -27,12 +27,15 @@ use googletest::prelude::ge;
 use googletest::prelude::le;
 use googletest::prelude::lt;
 
+use super::ReqF64;
+use super::req_f64;
+
 #[test]
 fn bounds_are_nested_and_in_unit_interval() {
-    let mut sketch = ReqSketch::new(12, RankAccuracy::HighRank).unwrap();
+    let mut sketch: ReqSketch<ReqF64> = ReqSketch::new(12, 
RankAccuracy::HighRank).unwrap();
 
     for i in 0..50_000 {
-        sketch.update(i as f64);
+        sketch.update(req_f64(i as f64));
     }
 
     for rank in [0.01, 0.05, 0.1, 0.25, 0.5, 0.75, 0.9, 0.95, 0.99, 0.999] {
@@ -60,18 +63,18 @@ fn bounds_are_nested_and_in_unit_interval() {
 
 #[test]
 fn theoretical_error_bounds_cover_uniform_quantiles() -> Result<(), Error> {
-    let mut sketch = ReqSketch::default();
+    let mut sketch: ReqSketch<ReqF64> = ReqSketch::default();
     let n = 50_000;
 
     for i in 0..n {
-        sketch.update(i as f64);
+        sketch.update(req_f64(i as f64));
     }
 
     for rank in [
         0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.4, 0.5, 0.6, 0.7, 0.75, 0.8, 
0.85, 0.9, 0.92,
         0.95, 0.97, 0.98, 0.99, 0.995, 0.999,
     ] {
-        let true_quantile = rank * (n - 1) as f64;
+        let true_quantile = req_f64(rank * (n - 1) as f64);
         let estimated_rank = sketch.rank(&true_quantile, 
SearchCriteria::Inclusive)?;
         let lower = sketch.rank_lower_bound(rank, 3);
         let upper = sketch.rank_upper_bound(rank, 3);
@@ -84,12 +87,12 @@ fn theoretical_error_bounds_cover_uniform_quantiles() -> 
Result<(), Error> {
 #[test]
 fn hra_and_lra_bounds_are_tighter_at_their_target_end() -> Result<(), Error> {
     for rank in [0.05, 0.25, 0.5, 0.75, 0.95] {
-        let mut hra = ReqSketch::new(12, RankAccuracy::HighRank).unwrap();
-        let mut lra = ReqSketch::new(12, RankAccuracy::LowRank).unwrap();
+        let mut hra: ReqSketch<ReqF64> = ReqSketch::new(12, 
RankAccuracy::HighRank).unwrap();
+        let mut lra: ReqSketch<ReqF64> = ReqSketch::new(12, 
RankAccuracy::LowRank).unwrap();
 
         for i in 0..10_000 {
-            hra.update(i as f64);
-            lra.update(i as f64);
+            hra.update(req_f64(i as f64));
+            lra.update(req_f64(i as f64));
         }
 
         let hra_error =
@@ -109,10 +112,10 @@ fn hra_and_lra_bounds_are_tighter_at_their_target_end() 
-> Result<(), Error> {
 
 #[test]
 fn exact_mode_bounds_are_tight() {
-    let mut sketch = ReqSketch::default();
+    let mut sketch: ReqSketch<ReqF64> = ReqSketch::default();
 
     for i in 0..20 {
-        sketch.update(i as f64);
+        sketch.update(req_f64(i as f64));
     }
 
     assert!(!sketch.is_estimation_mode());
@@ -126,17 +129,17 @@ fn exact_mode_bounds_are_tight() {
 
 #[test]
 fn high_rank_accuracy_matches_tight_thresholds() {
-    let mut sketch = ReqSketch::default();
+    let mut sketch: ReqSketch<ReqF64> = ReqSketch::default();
     let n = 50_000;
 
     for i in 0..n {
-        sketch.update(i as f64);
+        sketch.update(req_f64(i as f64));
     }
 
     assert_eq!(sketch.n(), n as u64);
 
     for rank in [0.5, 0.9, 0.95, 0.99, 0.999] {
-        let true_quantile = rank * (n - 1) as f64;
+        let true_quantile = req_f64(rank * (n - 1) as f64);
         let estimated_rank = sketch
             .rank(&true_quantile, SearchCriteria::Inclusive)
             .expect("rank should succeed");
diff --git a/tests-integration/tests/req_test/core.rs 
b/tests-integration/tests/req_test/core.rs
index 375d03e..0b43595 100644
--- a/tests-integration/tests/req_test/core.rs
+++ b/tests-integration/tests/req_test/core.rs
@@ -33,9 +33,14 @@ use googletest::prelude::lt;
 use googletest::prelude::near;
 use googletest::prelude::none;
 
+use super::ReqF32;
+use super::ReqF64;
+use super::req_f32;
+use super::req_f64;
+
 #[test]
 fn empty_sketch_has_default_state_and_rejects_queries() {
-    let sketch: ReqSketch<f32> = ReqSketch::default();
+    let sketch: ReqSketch<ReqF64> = ReqSketch::default();
 
     assert_eq!(sketch.k(), 12);
     assert!(sketch.is_empty());
@@ -46,7 +51,7 @@ fn empty_sketch_has_default_state_and_rejects_queries() {
     assert_that!(sketch.max_item(), none());
 
     assert_that!(
-        sketch.rank(&0.0, SearchCriteria::Inclusive),
+        sketch.rank(&req_f64(0.0), SearchCriteria::Inclusive),
         err(anything())
     );
     assert_that!(
@@ -54,66 +59,66 @@ fn empty_sketch_has_default_state_and_rejects_queries() {
         err(anything())
     );
     assert_that!(
-        sketch.pmf(&[0.0], SearchCriteria::Inclusive),
+        sketch.pmf(&[req_f64(0.0)], SearchCriteria::Inclusive),
         err(anything())
     );
     assert_that!(
-        sketch.cdf(&[0.0], SearchCriteria::Inclusive),
+        sketch.cdf(&[req_f64(0.0)], SearchCriteria::Inclusive),
         err(anything())
     );
 }
 
 #[test]
 fn single_value_hra_answers_exactly() {
-    let mut sketch = ReqSketch::default();
-    sketch.update(1.0f32);
+    let mut sketch: ReqSketch<ReqF32> = ReqSketch::default();
+    sketch.update(req_f32(1.0));
 
     assert!(!sketch.is_empty());
     assert!(!sketch.is_estimation_mode());
     assert_eq!(sketch.n(), 1);
     assert_eq!(sketch.num_retained(), 1);
-    assert_eq!(sketch.min_item(), Some(&1.0));
-    assert_eq!(sketch.max_item(), Some(&1.0));
+    assert_eq!(sketch.min_item().copied(), Some(req_f32(1.0)));
+    assert_eq!(sketch.max_item().copied(), Some(req_f32(1.0)));
 
     assert_that!(
         sketch
-            .rank(&1.0, SearchCriteria::Exclusive)
+            .rank(&req_f32(1.0), SearchCriteria::Exclusive)
             .expect("rank should succeed"),
         approx_eq(0.0)
     );
     assert_that!(
         sketch
-            .rank(&1.0, SearchCriteria::Inclusive)
+            .rank(&req_f32(1.0), SearchCriteria::Inclusive)
             .expect("rank should succeed"),
         approx_eq(1.0)
     );
     assert_that!(
         sketch
-            .rank(&1.1, SearchCriteria::Exclusive)
+            .rank(&req_f32(2.0), SearchCriteria::Exclusive)
             .expect("rank should succeed"),
         approx_eq(1.0)
     );
     assert_that!(
         sketch
-            .rank(&f32::INFINITY, SearchCriteria::Inclusive)
+            .rank(&req_f32(f32::MAX), SearchCriteria::Inclusive)
             .expect("rank should succeed"),
         approx_eq(1.0)
     );
 
     for rank in [0.0, 0.5, 1.0] {
-        assert_that!(
+        assert_eq!(
             sketch
                 .quantile(rank, SearchCriteria::Exclusive)
                 .expect("quantile should succeed"),
-            approx_eq(1.0)
+            req_f32(1.0)
         );
     }
 }
 
 #[test]
 fn single_value_lra_preserves_configuration() {
-    let mut sketch = ReqSketch::<f32>::new(12, RankAccuracy::LowRank).unwrap();
-    sketch.update(1.0f32);
+    let mut sketch = ReqSketch::<ReqF64>::new(12, 
RankAccuracy::LowRank).unwrap();
+    sketch.update(req_f64(1.0));
 
     assert_eq!(sketch.rank_accuracy(), RankAccuracy::LowRank);
     assert!(!sketch.is_empty());
@@ -124,12 +129,12 @@ fn single_value_lra_preserves_configuration() {
 
 #[test]
 fn repeated_values_respect_search_criteria() {
-    let mut sketch = ReqSketch::default();
+    let mut sketch: ReqSketch<ReqF64> = ReqSketch::default();
     for _ in 0..3 {
-        sketch.update(1.0f32);
+        sketch.update(req_f64(1.0));
     }
     for _ in 0..3 {
-        sketch.update(2.0f32);
+        sketch.update(req_f64(2.0));
     }
 
     assert!(!sketch.is_estimation_mode());
@@ -138,25 +143,25 @@ fn repeated_values_respect_search_criteria() {
 
     assert_that!(
         sketch
-            .rank(&1.0, SearchCriteria::Exclusive)
+            .rank(&req_f64(1.0), SearchCriteria::Exclusive)
             .expect("rank should succeed"),
         approx_eq(0.0)
     );
     assert_that!(
         sketch
-            .rank(&1.0, SearchCriteria::Inclusive)
+            .rank(&req_f64(1.0), SearchCriteria::Inclusive)
             .expect("rank should succeed"),
         approx_eq(0.5)
     );
     assert_that!(
         sketch
-            .rank(&2.0, SearchCriteria::Exclusive)
+            .rank(&req_f64(2.0), SearchCriteria::Exclusive)
             .expect("rank should succeed"),
         approx_eq(0.5)
     );
     assert_that!(
         sketch
-            .rank(&2.0, SearchCriteria::Inclusive)
+            .rank(&req_f64(2.0), SearchCriteria::Inclusive)
             .expect("rank should succeed"),
         approx_eq(1.0)
     );
@@ -164,28 +169,28 @@ fn repeated_values_respect_search_criteria() {
 
 #[test]
 fn estimation_mode_compresses_and_keeps_min_max() {
-    let mut sketch = ReqSketch::default();
+    let mut sketch: ReqSketch<ReqF64> = ReqSketch::default();
     let n = 100_000;
 
     for i in 0..n {
-        sketch.update(i as f32);
+        sketch.update(req_f64(i as f64));
     }
 
     assert!(!sketch.is_empty());
     assert!(sketch.is_estimation_mode());
     assert_eq!(sketch.n(), n);
     assert_that!(sketch.num_retained(), lt(n as u32));
-    assert_eq!(sketch.min_item(), Some(&0.0));
-    assert_eq!(sketch.max_item(), Some(&((n - 1) as f32)));
+    assert_eq!(sketch.min_item().copied(), Some(req_f64(0.0)));
+    assert_eq!(sketch.max_item().copied(), Some(req_f64((n - 1) as f64)));
 
     let r0 = sketch
-        .rank(&0.0, SearchCriteria::Exclusive)
+        .rank(&req_f64(0.0), SearchCriteria::Exclusive)
         .expect("rank should succeed");
     let rmid = sketch
-        .rank(&(n as f32 / 2.0), SearchCriteria::Exclusive)
+        .rank(&req_f64((n / 2) as f64), SearchCriteria::Exclusive)
         .expect("rank should succeed");
     let rmax = sketch
-        .rank(&(n as f32), SearchCriteria::Exclusive)
+        .rank(&req_f64(n as f64), SearchCriteria::Exclusive)
         .expect("rank should succeed");
 
     assert_that!(r0, near(0.0, 1e-3));
@@ -194,56 +199,43 @@ fn estimation_mode_compresses_and_keeps_min_max() {
 }
 
 #[test]
-fn nan_updates_are_silently_skipped_for_f64() {
-    let mut sketch: ReqSketch<f64> = ReqSketch::default();
-    sketch.update(f64::NAN);
-    sketch.update(f64::NAN);
-    assert!(sketch.is_empty());
-    assert_eq!(sketch.n(), 0);
-
-    sketch.update(1.0);
-    sketch.update(f64::NAN);
-    sketch.update(2.0);
-    assert_eq!(sketch.n(), 2);
-    assert_eq!(sketch.min_item(), Some(&1.0));
-    assert_eq!(sketch.max_item(), Some(&2.0));
-}
+fn req_float_adapts_the_non_nan_numeric_order() {
+    assert!(ReqF32::new(f32::NAN).is_err());
+    assert!(ReqF64::new(f64::NAN).is_err());
 
-#[test]
-fn nan_updates_are_silently_skipped_for_f32() {
-    let mut sketch: ReqSketch<f32> = ReqSketch::default();
-    sketch.update(f32::NAN);
-    assert!(sketch.is_empty());
-    assert_eq!(sketch.n(), 0);
+    let negative_zero = req_f64(-0.0);
+    let positive_zero = req_f64(0.0);
+    assert_eq!(negative_zero, positive_zero);
+    assert_eq!(negative_zero.cmp(&positive_zero), std::cmp::Ordering::Equal);
 
-    sketch.update(5.0f32);
-    sketch.update(f32::NAN);
-    assert_eq!(sketch.n(), 1);
-    assert_eq!(
-        sketch
-            .quantile(0.5, SearchCriteria::Inclusive)
-            .expect("quantile should succeed"),
-        5.0f32
-    );
+    let negative_infinity = req_f64(f64::NEG_INFINITY);
+    let infinity = req_f64(f64::INFINITY);
+    assert!(negative_infinity < infinity);
 }
 
 #[test]
 fn small_edge_cases_answer_reasonably() -> Result<(), Error> {
-    let mut single = ReqSketch::default();
-    single.update(42.0);
-    assert_eq!(single.quantile(0.5, SearchCriteria::Inclusive)?, 42.0);
+    let mut single: ReqSketch<ReqF64> = ReqSketch::default();
+    single.update(req_f64(42.0));
+    assert_eq!(
+        single.quantile(0.5, SearchCriteria::Inclusive)?,
+        req_f64(42.0)
+    );
 
     let mut two_values = ReqSketch::default();
-    two_values.update(1.0);
-    two_values.update(100.0);
+    two_values.update(req_f64(1.0));
+    two_values.update(req_f64(100.0));
     let median = two_values.quantile(0.5, SearchCriteria::Inclusive)?;
-    assert_that!(median, all!(ge(1.0), le(100.0)));
+    assert_that!(*median, all!(ge(1.0), le(100.0)));
 
     let mut duplicates = ReqSketch::default();
     for _ in 0..100 {
-        duplicates.update(42.0);
+        duplicates.update(req_f64(42.0));
     }
-    assert_eq!(duplicates.quantile(0.5, SearchCriteria::Inclusive)?, 42.0);
+    assert_eq!(
+        duplicates.quantile(0.5, SearchCriteria::Inclusive)?,
+        req_f64(42.0)
+    );
 
     Ok(())
 }
@@ -251,14 +243,14 @@ fn small_edge_cases_answer_reasonably() -> Result<(), 
Error> {
 #[test]
 fn new_validates_k() {
     assert_that!(
-        ReqSketch::<f64>::new(0, RankAccuracy::HighRank),
+        ReqSketch::<ReqF64>::new(0, RankAccuracy::HighRank),
         err(anything())
     );
-    let error = ReqSketch::<f64>::new(3, RankAccuracy::HighRank).unwrap_err();
+    let error = ReqSketch::<ReqF64>::new(3, 
RankAccuracy::HighRank).unwrap_err();
     assert_eq!(error.kind(), ErrorKind::InvalidArgument);
     assert_that!(
-        ReqSketch::<f64>::new(4096, RankAccuracy::HighRank),
+        ReqSketch::<ReqF64>::new(4096, RankAccuracy::HighRank),
         err(anything())
     );
-    assert!(ReqSketch::<f64>::new(12, RankAccuracy::HighRank).is_ok());
+    assert!(ReqSketch::<ReqF64>::new(12, RankAccuracy::HighRank).is_ok());
 }
diff --git a/tests-integration/tests/req_test/main.rs 
b/tests-integration/tests/req_test/generic.rs
similarity index 58%
copy from tests-integration/tests/req_test/main.rs
copy to tests-integration/tests/req_test/generic.rs
index 4633a38..26da858 100644
--- a/tests-integration/tests/req_test/main.rs
+++ b/tests-integration/tests/req_test/generic.rs
@@ -15,11 +15,23 @@
 // specific language governing permissions and limitations
 // under the License.
 
-mod accuracy;
-mod bounds;
-mod core;
-mod merge;
-mod property;
-mod query;
-mod sorted_view_api;
-mod structure;
+use datasketches::req::ReqSketch;
+use datasketches::req::SearchCriteria;
+
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
+struct Reading(i32);
+
+#[test]
+fn custom_items_do_not_need_serialization() {
+    let mut sketch = ReqSketch::default();
+    sketch.update(Reading(30));
+    sketch.update(Reading(10));
+    sketch.update(Reading(20));
+
+    assert_eq!(sketch.min_item(), Some(&Reading(10)));
+    assert_eq!(sketch.max_item(), Some(&Reading(30)));
+    assert_eq!(
+        sketch.quantile(0.5, SearchCriteria::Inclusive).unwrap(),
+        Reading(20)
+    );
+}
diff --git a/tests-integration/tests/req_test/main.rs 
b/tests-integration/tests/req_test/main.rs
index 4633a38..a76589b 100644
--- a/tests-integration/tests/req_test/main.rs
+++ b/tests-integration/tests/req_test/main.rs
@@ -15,9 +15,23 @@
 // specific language governing permissions and limitations
 // under the License.
 
+use datasketches::req::ReqFloat;
+
+type ReqF32 = ReqFloat<f32>;
+type ReqF64 = ReqFloat<f64>;
+
+fn req_f32(value: f32) -> ReqF32 {
+    ReqF32::new(value).unwrap()
+}
+
+fn req_f64(value: f64) -> ReqF64 {
+    ReqF64::new(value).unwrap()
+}
+
 mod accuracy;
 mod bounds;
 mod core;
+mod generic;
 mod merge;
 mod property;
 mod query;
diff --git a/tests-integration/tests/req_test/merge.rs 
b/tests-integration/tests/req_test/merge.rs
index 78b6c74..6f1b9ca 100644
--- a/tests-integration/tests/req_test/merge.rs
+++ b/tests-integration/tests/req_test/merge.rs
@@ -25,18 +25,21 @@ use googletest::prelude::anything;
 use googletest::prelude::err;
 use googletest::prelude::near;
 
+use super::ReqF64;
+use super::req_f64;
+
 #[test]
 fn merge_into_empty_preserves_source_distribution() {
-    let mut target: ReqSketch<f32> = ReqSketch::new(40, 
RankAccuracy::HighRank).unwrap();
-    let mut source: ReqSketch<f32> = ReqSketch::new(40, 
RankAccuracy::HighRank).unwrap();
+    let mut target: ReqSketch<ReqF64> = ReqSketch::new(40, 
RankAccuracy::HighRank).unwrap();
+    let mut source: ReqSketch<ReqF64> = ReqSketch::new(40, 
RankAccuracy::HighRank).unwrap();
 
     for i in 0..1000 {
-        source.update(i as f32);
+        source.update(req_f64(i as f64));
     }
 
     target.merge(&source).expect("merge should succeed");
-    assert_eq!(target.min_item(), Some(&0.0));
-    assert_eq!(target.max_item(), Some(&999.0));
+    assert_eq!(target.min_item().copied(), Some(req_f64(0.0)));
+    assert_eq!(target.max_item().copied(), Some(req_f64(999.0)));
 
     let q25 = target
         .quantile(0.25, SearchCriteria::Inclusive)
@@ -48,30 +51,30 @@ fn merge_into_empty_preserves_source_distribution() {
         .quantile(0.75, SearchCriteria::Inclusive)
         .expect("quantile should succeed");
     let r50 = target
-        .rank(&500.0, SearchCriteria::Inclusive)
+        .rank(&req_f64(500.0), SearchCriteria::Inclusive)
         .expect("rank should succeed");
 
-    assert_that!(q25, near(250.0, 250.0 * 0.01));
-    assert_that!(q50, near(500.0, 500.0 * 0.01));
-    assert_that!(q75, near(750.0, 750.0 * 0.01));
+    assert_that!(*q25, near(250.0, 250.0 * 0.01));
+    assert_that!(*q50, near(500.0, 500.0 * 0.01));
+    assert_that!(*q75, near(750.0, 750.0 * 0.01));
     assert_that!(r50, near(0.5, 0.5 * 0.01));
 }
 
 #[test]
 fn merge_two_ranges_preserves_distribution() {
-    let mut left: ReqSketch<f32> = ReqSketch::new(100, 
RankAccuracy::HighRank).unwrap();
-    let mut right: ReqSketch<f32> = ReqSketch::new(100, 
RankAccuracy::HighRank).unwrap();
+    let mut left: ReqSketch<ReqF64> = ReqSketch::new(100, 
RankAccuracy::HighRank).unwrap();
+    let mut right: ReqSketch<ReqF64> = ReqSketch::new(100, 
RankAccuracy::HighRank).unwrap();
 
     for i in 0..1000 {
-        left.update(i as f32);
+        left.update(req_f64(i as f64));
     }
     for i in 1000..2000 {
-        right.update(i as f32);
+        right.update(req_f64(i as f64));
     }
 
     left.merge(&right).expect("merge should succeed");
-    assert_eq!(left.min_item(), Some(&0.0));
-    assert_eq!(left.max_item(), Some(&1999.0));
+    assert_eq!(left.min_item().copied(), Some(req_f64(0.0)));
+    assert_eq!(left.max_item().copied(), Some(req_f64(1999.0)));
 
     let q25 = left
         .quantile(0.25, SearchCriteria::Inclusive)
@@ -83,42 +86,42 @@ fn merge_two_ranges_preserves_distribution() {
         .quantile(0.75, SearchCriteria::Inclusive)
         .expect("quantile should succeed");
     let r50 = left
-        .rank(&1000.0, SearchCriteria::Inclusive)
+        .rank(&req_f64(1000.0), SearchCriteria::Inclusive)
         .expect("rank should succeed");
 
-    assert_that!(q25, near(500.0, 500.0 * 0.02));
-    assert_that!(q50, near(1000.0, 1000.0 * 0.01));
-    assert_that!(q75, near(1500.0, 1500.0 * 0.01));
+    assert_that!(*q25, near(500.0, 500.0 * 0.02));
+    assert_that!(*q50, near(1000.0, 1000.0 * 0.01));
+    assert_that!(*q75, near(1500.0, 1500.0 * 0.01));
     assert_that!(r50, near(0.5, 0.5 * 0.01));
 }
 
 #[test]
 fn merge_rejects_incompatible_accuracy_modes() {
-    let mut high_rank = ReqSketch::default();
-    let low_rank: ReqSketch<f32> = ReqSketch::new(12, 
RankAccuracy::LowRank).unwrap();
+    let mut high_rank: ReqSketch<ReqF64> = ReqSketch::default();
+    let low_rank: ReqSketch<ReqF64> = ReqSketch::new(12, 
RankAccuracy::LowRank).unwrap();
 
-    high_rank.update(1.0);
+    high_rank.update(req_f64(1.0));
     assert_that!(high_rank.merge(&low_rank), err(anything()));
 }
 
 #[test]
 fn many_small_merges_preserve_count_bounds_and_median() {
-    let mut sketch = ReqSketch::default();
+    let mut sketch: ReqSketch<ReqF64> = ReqSketch::default();
 
     for batch in 0..100 {
         let mut batch_sketch = ReqSketch::default();
         for i in 0..100 {
-            batch_sketch.update((batch * 100 + i) as f64);
+            batch_sketch.update(req_f64((batch * 100 + i) as f64));
         }
         sketch.merge(&batch_sketch).expect("merge should succeed");
     }
 
     assert_eq!(sketch.n(), 10_000);
-    assert_eq!(sketch.min_item(), Some(&0.0));
-    assert_eq!(sketch.max_item(), Some(&9999.0));
+    assert_eq!(sketch.min_item().copied(), Some(req_f64(0.0)));
+    assert_eq!(sketch.max_item().copied(), Some(req_f64(9999.0)));
 
     let median = sketch
         .quantile(0.5, SearchCriteria::Inclusive)
         .expect("quantile should succeed");
-    assert_that!(median, near(4999.5, 500.0));
+    assert_that!(*median, near(4999.5, 500.0));
 }
diff --git a/tests-integration/tests/req_test/property.rs 
b/tests-integration/tests/req_test/property.rs
index b70952d..e993c4d 100644
--- a/tests-integration/tests/req_test/property.rs
+++ b/tests-integration/tests/req_test/property.rs
@@ -23,16 +23,19 @@ use quickcheck::Gen;
 use quickcheck::QuickCheck;
 use quickcheck::TestResult;
 
+use super::ReqF64;
+use super::req_f64;
+
 #[test]
 fn prop_quantile_rank_consistency() {
-    fn property(values: Vec<u64>) -> TestResult {
+    fn property(values: Vec<u32>) -> TestResult {
         if !(500..1500).contains(&values.len()) {
             return TestResult::discard();
         }
 
-        let mut sketch = ReqSketch::default();
+        let mut sketch: ReqSketch<ReqF64> = ReqSketch::default();
         for value in values {
-            sketch.update(value);
+            sketch.update(req_f64(value as f64));
         }
 
         // These sizes push the sketch past the compaction threshold, so the
@@ -68,23 +71,23 @@ fn prop_quantile_rank_consistency() {
         .tests(256)
         .min_tests_passed(256)
         .rng(Gen::new(1500))
-        .quickcheck(property as fn(Vec<u64>) -> TestResult);
+        .quickcheck(property as fn(Vec<u32>) -> TestResult);
 }
 
 #[test]
 fn prop_sketch_bounds() {
-    fn property(values: Vec<i64>) -> TestResult {
+    fn property(values: Vec<i32>) -> TestResult {
         if !(1..1000).contains(&values.len()) {
             return TestResult::discard();
         }
 
-        let mut sketch = ReqSketch::default();
+        let mut sketch: ReqSketch<ReqF64> = ReqSketch::default();
         for value in &values {
-            sketch.update(*value);
+            sketch.update(req_f64(*value as f64));
         }
 
-        let true_min = values.iter().copied().min().expect("values are 
non-empty");
-        let true_max = values.iter().copied().max().expect("values are 
non-empty");
+        let true_min = req_f64(values.iter().copied().min().expect("values are 
non-empty") as f64);
+        let true_max = req_f64(values.iter().copied().max().expect("values are 
non-empty") as f64);
 
         assert_eq!(sketch.min_item(), Some(&true_min));
         assert_eq!(sketch.max_item(), Some(&true_max));
@@ -109,7 +112,7 @@ fn prop_sketch_bounds() {
         .tests(256)
         .min_tests_passed(256)
         .rng(Gen::new(1000))
-        .quickcheck(property as fn(Vec<i64>) -> TestResult);
+        .quickcheck(property as fn(Vec<i32>) -> TestResult);
 }
 
 #[test]
@@ -119,9 +122,9 @@ fn prop_rank_monotonicity() {
             return TestResult::discard();
         }
 
-        let mut sketch = ReqSketch::default();
+        let mut sketch: ReqSketch<ReqF64> = ReqSketch::default();
         for value in values {
-            sketch.update(value);
+            sketch.update(req_f64(value as f64));
         }
 
         let mut last_rank = -1.0;
@@ -134,7 +137,7 @@ fn prop_rank_monotonicity() {
             u32::MAX,
         ] {
             let rank = sketch
-                .rank(&value, SearchCriteria::Inclusive)
+                .rank(&req_f64(value as f64), SearchCriteria::Inclusive)
                 .expect("rank should succeed");
             assert!(rank >= last_rank, "rank {} after {}", rank, last_rank);
             assert!((0.0..=1.0).contains(&rank), "rank {} out of bounds", 
rank);
diff --git a/tests-integration/tests/req_test/query.rs 
b/tests-integration/tests/req_test/query.rs
index 30f3092..0404dec 100644
--- a/tests-integration/tests/req_test/query.rs
+++ b/tests-integration/tests/req_test/query.rs
@@ -27,11 +27,14 @@ use googletest::prelude::le;
 use googletest::prelude::lt;
 use googletest::prelude::near;
 
+use super::ReqF64;
+use super::req_f64;
+
 #[test]
 fn exact_mode_rank_quantile_pmf_and_cdf_match_reference() {
-    let mut sketch = ReqSketch::default();
+    let mut sketch: ReqSketch<ReqF64> = ReqSketch::default();
     for i in 1..=10 {
-        sketch.update(i as f32);
+        sketch.update(req_f64(i as f64));
     }
 
     assert!(!sketch.is_estimation_mode());
@@ -41,7 +44,7 @@ fn exact_mode_rank_quantile_pmf_and_cdf_match_reference() {
     for (value, expected) in [(1.0, 0.0), (2.0, 0.1), (6.0, 0.5), (9.0, 0.8), 
(10.0, 0.9)] {
         assert_that!(
             sketch
-                .rank(&value, SearchCriteria::Exclusive)
+                .rank(&req_f64(value), SearchCriteria::Exclusive)
                 .expect("rank should succeed"),
             near(expected, 1e-6)
         );
@@ -50,31 +53,31 @@ fn exact_mode_rank_quantile_pmf_and_cdf_match_reference() {
     for (value, expected) in [(1.0, 0.1), (2.0, 0.2), (5.0, 0.5), (9.0, 0.9), 
(10.0, 1.0)] {
         assert_that!(
             sketch
-                .rank(&value, SearchCriteria::Inclusive)
+                .rank(&req_f64(value), SearchCriteria::Inclusive)
                 .expect("rank should succeed"),
             near(expected, 1e-6)
         );
     }
 
     for (rank, expected) in [(0.0, 1.0), (0.1, 2.0), (0.5, 6.0), (0.9, 10.0), 
(1.0, 10.0)] {
-        assert_that!(
-            sketch
+        assert_eq!(
+            *sketch
                 .quantile(rank, SearchCriteria::Exclusive)
                 .expect("quantile should succeed"),
-            near(expected, 1e-6)
+            expected
         );
     }
 
     for (rank, expected) in [(0.0, 1.0), (0.1, 1.0), (0.5, 5.0), (0.9, 9.0), 
(1.0, 10.0)] {
-        assert_that!(
-            sketch
+        assert_eq!(
+            *sketch
                 .quantile(rank, SearchCriteria::Inclusive)
                 .expect("quantile should succeed"),
-            near(expected, 1e-6)
+            expected
         );
     }
 
-    let splits = [2.0, 6.0, 9.0];
+    let splits = [2.0, 6.0, 9.0].map(req_f64);
     let cdf = sketch
         .cdf(&splits, SearchCriteria::Exclusive)
         .expect("cdf should succeed");
@@ -94,12 +97,12 @@ fn exact_mode_rank_quantile_pmf_and_cdf_match_reference() {
 
 #[test]
 fn pmf_and_cdf_are_consistent() {
-    let mut sketch = ReqSketch::default();
+    let mut sketch: ReqSketch<ReqF64> = ReqSketch::default();
     for i in 0..1000 {
-        sketch.update(i as f64);
+        sketch.update(req_f64(i as f64));
     }
 
-    let split_points = [100.0, 300.0, 500.0, 700.0, 900.0];
+    let split_points = [100.0, 300.0, 500.0, 700.0, 900.0].map(req_f64);
     let pmf = sketch
         .pmf(&split_points, SearchCriteria::Inclusive)
         .expect("pmf should succeed");
@@ -119,12 +122,12 @@ fn pmf_and_cdf_are_consistent() {
 
 #[test]
 fn rank_is_monotonic_and_bounded() {
-    let mut sketch = ReqSketch::default();
+    let mut sketch: ReqSketch<ReqF64> = ReqSketch::default();
     for i in 0..10_000 {
-        sketch.update(i as f64);
+        sketch.update(req_f64(i as f64));
     }
 
-    let test_values: Vec<f64> = (0..10_000).step_by(1000).map(|i| i as 
f64).collect();
+    let test_values = (0..10_000).step_by(1000).map(|value| req_f64(value as 
f64));
     let mut last_rank = 0.0;
 
     for value in test_values {
@@ -139,9 +142,9 @@ fn rank_is_monotonic_and_bounded() {
 
 #[test]
 fn quantiles_are_monotonic() -> Result<(), Error> {
-    let mut sketch = ReqSketch::default();
+    let mut sketch: ReqSketch<ReqF64> = ReqSketch::default();
     for i in 0..10_000 {
-        sketch.update(i as f64);
+        sketch.update(req_f64(i as f64));
     }
 
     let ranks = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9];
@@ -149,8 +152,8 @@ fn quantiles_are_monotonic() -> Result<(), Error> {
 
     for rank in ranks {
         let quantile = sketch.quantile(rank, SearchCriteria::Inclusive)?;
-        assert_that!(quantile, ge(previous));
-        previous = quantile;
+        assert_that!(*quantile, ge(previous));
+        previous = *quantile;
     }
 
     Ok(())
@@ -158,9 +161,9 @@ fn quantiles_are_monotonic() -> Result<(), Error> {
 
 #[test]
 fn rank_quantile_round_trip_is_consistent() -> Result<(), Error> {
-    let mut sketch = ReqSketch::default();
+    let mut sketch: ReqSketch<ReqF64> = ReqSketch::default();
     for i in 0..10_000 {
-        sketch.update(i as f64);
+        sketch.update(req_f64(i as f64));
     }
 
     for target_rank in [0.1, 0.25, 0.5, 0.75, 0.9] {
@@ -175,12 +178,12 @@ fn rank_quantile_round_trip_is_consistent() -> Result<(), 
Error> {
 
 #[test]
 fn search_criteria_rank_consistency() -> Result<(), Error> {
-    let mut sketch = ReqSketch::default();
+    let mut sketch: ReqSketch<ReqF64> = ReqSketch::default();
     for i in 0..1000 {
-        sketch.update(i as f64);
+        sketch.update(req_f64(i as f64));
     }
 
-    for value in [100.0, 250.0, 500.0, 750.0] {
+    for value in [100.0, 250.0, 500.0, 750.0].map(req_f64) {
         let inclusive_rank = sketch.rank(&value, SearchCriteria::Inclusive)?;
         let exclusive_rank = sketch.rank(&value, SearchCriteria::Exclusive)?;
 
@@ -194,16 +197,22 @@ fn search_criteria_rank_consistency() -> Result<(), 
Error> {
 
 #[test]
 fn signed_zeros_share_rank_and_cannot_be_distinct_splits() -> Result<(), 
Error> {
-    let mut sketch = ReqSketch::default();
-    sketch.update(-0.0_f64);
-    sketch.update(0.0_f64);
+    let mut sketch: ReqSketch<ReqF64> = ReqSketch::default();
+    let negative_zero = req_f64(-0.0);
+    let positive_zero = req_f64(0.0);
+    sketch.update(negative_zero);
+    sketch.update(positive_zero);
 
-    for value in [-0.0, 0.0] {
+    for value in [negative_zero, positive_zero] {
         assert_eq!(sketch.rank(&value, SearchCriteria::Exclusive)?, 0.0);
         assert_eq!(sketch.rank(&value, SearchCriteria::Inclusive)?, 1.0);
     }
 
-    assert!(sketch.pmf(&[-0.0, 0.0], SearchCriteria::Inclusive).is_err());
+    assert!(
+        sketch
+            .pmf(&[negative_zero, positive_zero], SearchCriteria::Inclusive)
+            .is_err()
+    );
 
     Ok(())
 }
diff --git a/tests-integration/tests/req_test/sorted_view_api.rs 
b/tests-integration/tests/req_test/sorted_view_api.rs
index 32a518b..6882f2a 100644
--- a/tests-integration/tests/req_test/sorted_view_api.rs
+++ b/tests-integration/tests/req_test/sorted_view_api.rs
@@ -32,28 +32,33 @@ use googletest::prelude::ge;
 use googletest::prelude::lt;
 use googletest::prelude::near;
 
-fn populated_sketch(n: u64) -> ReqSketch<f64> {
+use super::ReqF64;
+use super::req_f64;
+
+fn populated_sketch(n: i64) -> ReqSketch<ReqF64> {
     let mut sketch = ReqSketch::default();
     for i in 0..n {
-        sketch.update(i as f64);
+        sketch.update(req_f64(i as f64));
     }
     sketch
 }
 
 /// All distribution queries must work through a shared (`&self`) reference.
-fn query_through_shared_ref(sketch: &ReqSketch<f64>) {
+fn query_through_shared_ref(sketch: &ReqSketch<ReqF64>) {
     sketch
         .quantile(0.5, SearchCriteria::Inclusive)
         .expect("quantile");
     sketch
         .quantiles(&[0.25, 0.5, 0.75], SearchCriteria::Inclusive)
         .expect("quantiles");
-    sketch.rank(&50.0, SearchCriteria::Inclusive).expect("rank");
     sketch
-        .pmf(&[10.0, 50.0], SearchCriteria::Inclusive)
+        .rank(&req_f64(50.0), SearchCriteria::Inclusive)
+        .expect("rank");
+    sketch
+        .pmf(&[req_f64(10.0), req_f64(50.0)], SearchCriteria::Inclusive)
         .expect("pmf");
     sketch
-        .cdf(&[10.0, 50.0], SearchCriteria::Inclusive)
+        .cdf(&[req_f64(10.0), req_f64(50.0)], SearchCriteria::Inclusive)
         .expect("cdf");
     assert!(!sketch.sorted_view().is_empty());
 }
@@ -68,13 +73,13 @@ fn queries_work_through_shared_reference() {
 fn sorted_view_is_an_owned_snapshot() {
     let mut sketch = populated_sketch(100);
 
-    let view: SortedView<f64> = sketch.sorted_view();
+    let view: SortedView<ReqF64> = sketch.sorted_view();
     assert_eq!(view.total_weight(), 100);
 
     // Updating the sketch while the view is alive must compile (owned view)
     // and must not affect the snapshot.
     for i in 100..200 {
-        sketch.update(i as f64);
+        sketch.update(req_f64(i as f64));
     }
     assert_eq!(view.total_weight(), 100);
 
@@ -85,7 +90,7 @@ fn sorted_view_is_an_owned_snapshot() {
 
 #[test]
 fn sorted_view_on_empty_sketch_is_an_empty_view() {
-    let sketch: ReqSketch<f64> = ReqSketch::default();
+    let sketch: ReqSketch<ReqF64> = ReqSketch::default();
     let view = sketch.sorted_view();
     assert!(view.is_empty());
     assert_eq!(view.len(), 0);
@@ -99,13 +104,13 @@ fn sorted_view_on_empty_sketch_is_an_empty_view() {
 
 #[test]
 fn empty_sketch_pmf_cdf_report_error() {
-    let sketch: ReqSketch<f64> = ReqSketch::default();
+    let sketch: ReqSketch<ReqF64> = ReqSketch::default();
     assert_that!(
-        sketch.pmf(&[1.0], SearchCriteria::Inclusive),
+        sketch.pmf(&[req_f64(1.0)], SearchCriteria::Inclusive),
         err(anything())
     );
     assert_that!(
-        sketch.cdf(&[1.0], SearchCriteria::Inclusive),
+        sketch.cdf(&[req_f64(1.0)], SearchCriteria::Inclusive),
         err(anything())
     );
 }
@@ -114,49 +119,16 @@ fn empty_sketch_pmf_cdf_report_error() {
 fn view_rank_is_primary_query_name() {
     let sketch = populated_sketch(10);
     let view = sketch.sorted_view();
-    let r = view.rank(&5.0, SearchCriteria::Inclusive).expect("rank");
+    let r = view
+        .rank(&req_f64(5.0), SearchCriteria::Inclusive)
+        .expect("rank");
     assert_that!(r, near(0.6, 1e-10));
 }
 
-#[test]
-fn nan_query_items_and_split_points_are_rejected() {
-    let sketch = populated_sketch(100);
-    let error = sketch
-        .rank(&f64::NAN, SearchCriteria::Inclusive)
-        .unwrap_err();
-    assert_eq!(error.kind(), ErrorKind::InvalidArgument);
-    assert_eq!(
-        sketch
-            .pmf(&[f64::NAN], SearchCriteria::Inclusive)
-            .unwrap_err()
-            .kind(),
-        ErrorKind::InvalidArgument
-    );
-    assert_eq!(
-        sketch
-            .cdf(&[50.0, f64::NAN], SearchCriteria::Inclusive)
-            .unwrap_err()
-            .kind(),
-        ErrorKind::InvalidArgument
-    );
-
-    let view = sketch.sorted_view();
-    assert_that!(
-        view.rank(&f64::NAN, SearchCriteria::Inclusive),
-        err(anything())
-    );
-    assert_eq!(
-        view.pmf(&[f64::NAN], SearchCriteria::Inclusive)
-            .unwrap_err()
-            .kind(),
-        ErrorKind::InvalidArgument
-    );
-}
-
 #[test]
 fn error_precedence_empty_before_invalid_rank() {
     // On an empty sketch the emptiness is reported before the out-of-range 
rank.
-    let empty: ReqSketch<f64> = ReqSketch::default();
+    let empty: ReqSketch<ReqF64> = ReqSketch::default();
     let empty_err = empty.quantile(2.0, 
SearchCriteria::Inclusive).unwrap_err();
     assert_that!(empty_err.message(), contains_substring("empty"));
 
@@ -170,8 +142,8 @@ fn error_precedence_empty_before_invalid_rank() {
 #[test]
 fn view_is_send_and_sync() {
     fn assert_send_sync<T: Send + Sync>() {}
-    assert_send_sync::<SortedView<f64>>();
-    assert_send_sync::<ReqSketch<f64>>();
+    assert_send_sync::<SortedView<ReqF64>>();
+    assert_send_sync::<ReqSketch<ReqF64>>();
 }
 
 #[test]
@@ -190,6 +162,6 @@ fn concurrent_readers_share_the_sketch() {
         .collect();
     for handle in handles {
         let q = handle.join().expect("thread");
-        assert_that!(q, all!(ge(0.0), lt(1_000.0)));
+        assert_that!(*q, all!(ge(0.0), lt(1_000.0)));
     }
 }
diff --git a/tests-integration/tests/req_test/structure.rs 
b/tests-integration/tests/req_test/structure.rs
index cbe2838..e14cd79 100644
--- a/tests-integration/tests/req_test/structure.rs
+++ b/tests-integration/tests/req_test/structure.rs
@@ -26,11 +26,14 @@ use googletest::prelude::gt;
 use googletest::prelude::le;
 use googletest::prelude::predicate;
 
+use super::ReqF64;
+use super::req_f64;
+
 #[test]
 fn iterator_weights_sum_to_n_and_items_are_in_range() {
-    let mut sketch = ReqSketch::default();
+    let mut sketch: ReqSketch<ReqF64> = ReqSketch::default();
     for i in 0..1000 {
-        sketch.update(i as f64);
+        sketch.update(req_f64(i as f64));
     }
 
     let total_weight: u64 = sketch.iter().map(|(_, weight)| weight).sum();
@@ -46,12 +49,12 @@ fn iterator_weights_sum_to_n_and_items_are_in_range() {
 #[test]
 fn small_sketch_iterator_reports_unit_weights() {
     // Below the compaction threshold every retained item still has weight 1.
-    let mut sketch = ReqSketch::default();
+    let mut sketch: ReqSketch<ReqF64> = ReqSketch::default();
     for i in 0..10 {
-        sketch.update(i as f64);
+        sketch.update(req_f64(i as f64));
     }
 
-    let items: Vec<(f64, u64)> = sketch.iter().collect();
+    let items: Vec<(ReqF64, u64)> = sketch.iter().collect();
     assert_eq!(items.len(), 10);
     let weights: Vec<_> = items.iter().map(|&(_, weight)| weight).collect();
     assert_that!(weights, each(eq(&1)));
@@ -59,7 +62,7 @@ fn small_sketch_iterator_reports_unit_weights() {
 
 #[test]
 fn empty_sketch_iterator_yields_nothing() {
-    let sketch: ReqSketch<i32> = ReqSketch::default();
+    let sketch: ReqSketch<ReqF64> = ReqSketch::default();
     assert_eq!(sketch.iter().count(), 0);
 }
 
@@ -67,9 +70,9 @@ fn empty_sketch_iterator_yields_nothing() {
 fn compaction_promotes_surviving_items_to_higher_weights() {
     // After enough updates to trigger compaction, surviving items are 
promoted up a
     // level at double weight, so the maximum item weight exceeds 1.
-    let mut sketch = ReqSketch::default();
+    let mut sketch: ReqSketch<ReqF64> = ReqSketch::default();
     for i in 0..100_000 {
-        sketch.update(i as f64);
+        sketch.update(req_f64(i as f64));
     }
 
     let max_weight = sketch.iter().map(|(_, weight)| weight).max().unwrap();
diff --git a/tests-integration/tests/serde_tests/req.rs 
b/tests-integration/tests/serde_tests/req.rs
index 20e61d6..0138664 100644
--- a/tests-integration/tests/serde_tests/req.rs
+++ b/tests-integration/tests/serde_tests/req.rs
@@ -21,6 +21,7 @@ use std::fs;
 use std::path::PathBuf;
 
 use datasketches::req::RankAccuracy;
+use datasketches::req::ReqFloat;
 use datasketches::req::ReqSketch;
 use datasketches::req::ReqValue;
 use datasketches::req::SearchCriteria;
@@ -31,11 +32,22 @@ use googletest::prelude::ok;
 
 use crate::serialization_test_data;
 
+type ReqF32 = ReqFloat<f32>;
+type ReqF64 = ReqFloat<f64>;
+
+fn req_f32(value: f32) -> ReqF32 {
+    ReqFloat::<f32>::new(value).unwrap()
+}
+
+fn req_f64(value: f64) -> ReqF64 {
+    ReqFloat::<f64>::new(value).unwrap()
+}
+
 // ---------- Rust ↔ Rust round-trip ----------
 
 fn round_trip_one<T>(k: u16, ra: RankAccuracy, n: u64, make_item: impl Fn(u64) 
-> T)
 where
-    T: ReqValue + std::fmt::Debug + PartialEq,
+    T: Clone + Ord + ReqValue + std::fmt::Debug,
 {
     let mut a: ReqSketch<T> = ReqSketch::new(k, ra).unwrap();
     for i in 0..n {
@@ -56,7 +68,7 @@ fn round_trip_f64_matrix() {
     for &k in &[4u16, 12, 1024] {
         for &ra in &[RankAccuracy::HighRank, RankAccuracy::LowRank] {
             for &n in &[0u64, 1, 4, 5, 100, 10_000] {
-                round_trip_one::<f64>(k, ra, n, |i| i as f64);
+                round_trip_one::<ReqF64>(k, ra, n, |i| req_f64(i as f64));
             }
         }
     }
@@ -65,7 +77,7 @@ fn round_trip_f64_matrix() {
 #[test]
 fn round_trip_f32_basic() {
     for &n in &[0u64, 1, 4, 5, 1000] {
-        round_trip_one::<f32>(12, RankAccuracy::HighRank, n, |i| i as f32);
+        round_trip_one::<ReqF32>(12, RankAccuracy::HighRank, n, |i| req_f32(i 
as f32));
     }
 }
 
@@ -88,7 +100,7 @@ fn deserialize_truncated_preamble() {
     // Less than 8 bytes — can't even read the fixed preamble.
     for n in 0..8usize {
         let bytes = vec![0u8; n];
-        let result = ReqSketch::<f32>::deserialize(&bytes);
+        let result = ReqSketch::<ReqF32>::deserialize(&bytes);
         assert_that!(result, err(anything()), "preamble length: {n}");
     }
 }
@@ -106,7 +118,7 @@ fn deserialize_wrong_family_id() {
         0u8, // num_levels
         0u8, // num_raw_items
     ];
-    let result = ReqSketch::<f32>::deserialize(&bytes);
+    let result = ReqSketch::<ReqF32>::deserialize(&bytes);
     assert_that!(result, err(anything()));
     let err = result.unwrap_err();
     assert_eq!(
@@ -125,7 +137,7 @@ fn deserialize_wrong_serial_version() {
         17u8, 4u8, // IS_EMPTY
         12u8, 0u8, 0u8, 0u8,
     ];
-    let result = ReqSketch::<f32>::deserialize(&bytes);
+    let result = ReqSketch::<ReqF32>::deserialize(&bytes);
     assert_that!(result, err(anything()));
 }
 
@@ -133,7 +145,7 @@ fn deserialize_wrong_serial_version() {
 fn deserialize_invalid_preamble_ints() {
     // preamble_ints must be 2 (exact) or 4 (estimation). Try 3.
     let bytes = [3u8, 1, 17, 4, 12, 0, 0, 0];
-    let result = ReqSketch::<f32>::deserialize(&bytes);
+    let result = ReqSketch::<ReqF32>::deserialize(&bytes);
     assert_that!(result, err(anything()));
 }
 
@@ -148,7 +160,7 @@ fn deserialize_rejects_non_empty_zero_levels() {
         0u8, // num_levels=0 is invalid for non-empty sketches
         0u8,
     ];
-    let result = ReqSketch::<f32>::deserialize(&bytes);
+    let result = ReqSketch::<ReqF32>::deserialize(&bytes);
     assert_that!(result, err(anything()));
 }
 
@@ -161,7 +173,7 @@ fn deserialize_rejects_inconsistent_raw_items_header() {
         0u8, // invalid raw item count
     ];
     assert_that!(
-        ReqSketch::<f32>::deserialize(&raw_with_no_items),
+        ReqSketch::<ReqF32>::deserialize(&raw_with_no_items),
         err(anything())
     );
 
@@ -171,7 +183,7 @@ fn deserialize_rejects_inconsistent_raw_items_header() {
         1u8,
     ];
     assert_that!(
-        ReqSketch::<f32>::deserialize(&raw_with_two_levels),
+        ReqSketch::<ReqF32>::deserialize(&raw_with_two_levels),
         err(anything())
     );
 }
@@ -212,7 +224,7 @@ fn deserialize_truncated_estimation_mode() {
         0u8, /* num_raw_items
               * no payload — truncated */
     ];
-    let result = ReqSketch::<f32>::deserialize(&bytes);
+    let result = ReqSketch::<ReqF32>::deserialize(&bytes);
     assert_that!(result, err(anything()));
 }
 
@@ -226,28 +238,28 @@ fn deserialize_truncated_raw_items() {
         3u8, // num_raw_items=3 (but only 1 f32 supplied)
         0u8, 0, 0x80, 0x3f, // 1.0_f32 (only 1 of the 3 promised items)
     ];
-    let result = ReqSketch::<f32>::deserialize(&bytes);
+    let result = ReqSketch::<ReqF32>::deserialize(&bytes);
     assert_that!(result, err(anything()));
 }
 
 #[test]
 fn merge_preserves_order_across_serde_round_trip() {
-    let mut high = ReqSketch::<f64>::default();
-    let mut low = ReqSketch::<f64>::default();
+    let mut high = ReqSketch::<ReqF64>::default();
+    let mut low = ReqSketch::<ReqF64>::default();
 
     for value in 1000..=1072 {
-        high.update(value as f64);
+        high.update(req_f64(value as f64));
     }
     for value in 0..=72 {
-        low.update(value as f64);
+        low.update(req_f64(value as f64));
     }
 
     high.merge(&low).unwrap();
-    let restored = ReqSketch::<f64>::deserialize(&high.serialize()).unwrap();
+    let restored = 
ReqSketch::<ReqF64>::deserialize(&high.serialize()).unwrap();
     let view = restored.sorted_view();
 
     for value in 0..=1072 {
-        let value = value as f64;
+        let value = req_f64(value as f64);
         assert_eq!(
             restored.rank(&value, SearchCriteria::Inclusive).unwrap(),
             view.rank(&value, SearchCriteria::Inclusive).unwrap(),
@@ -281,9 +293,9 @@ fn exact_image(k: u16, items: &[f32]) -> Vec<u8> {
 }
 
 fn estimation_image(k: u16, n: u64) -> Vec<u8> {
-    let mut sketch = ReqSketch::<f32>::new(k, RankAccuracy::HighRank).unwrap();
+    let mut sketch = ReqSketch::<ReqF32>::new(k, 
RankAccuracy::HighRank).unwrap();
     for item in 1..=n {
-        sketch.update(item as f32);
+        sketch.update(req_f32(item as f32));
     }
     let bytes = sketch.serialize();
     assert!(bytes[6] > 1);
@@ -295,14 +307,14 @@ fn read_u64(bytes: &[u8], offset: usize) -> u64 {
 }
 
 fn assert_invalid_data(bytes: &[u8]) {
-    let error = ReqSketch::<f32>::deserialize(bytes).unwrap_err();
+    let error = ReqSketch::<ReqF32>::deserialize(bytes).unwrap_err();
     assert_eq!(error.kind(), ErrorKind::InvalidData);
 }
 
 #[test]
 fn canonical_exact_image_is_valid() {
     let bytes = exact_image(12, &[1.0, 2.0, 3.0, 4.0, 5.0]);
-    assert_that!(ReqSketch::<f32>::deserialize(&bytes), ok(anything()));
+    assert_that!(ReqSketch::<ReqF32>::deserialize(&bytes), ok(anything()));
 }
 
 #[test]
@@ -347,12 +359,12 @@ fn deserialize_accepts_java_minimum_section_schedule() {
         .copy_from_slice(&java_raw.to_le_bytes());
     bytes[compactor + NUM_SECTIONS_OFFSET] = 6;
 
-    let mut sketch = ReqSketch::<f32>::deserialize(&bytes).unwrap();
+    let mut sketch = ReqSketch::<ReqF32>::deserialize(&bytes).unwrap();
     for item in 1_251..=2_500 {
-        sketch.update(item as f32);
+        sketch.update(req_f32(item as f32));
     }
     let continued = sketch.serialize();
-    assert_that!(ReqSketch::<f32>::deserialize(&continued), ok(anything()));
+    assert_that!(ReqSketch::<ReqF32>::deserialize(&continued), ok(anything()));
 }
 
 #[test]
@@ -366,10 +378,10 @@ fn deserialize_accepts_safe_section_size_drift() {
 
     // One ULP below 5.0 rounds to a section size of 4 rather than 6.
     bytes[raw_offset..raw_offset + 4].copy_from_slice(&(raw_bits - 
1).to_le_bytes());
-    let mut sketch = ReqSketch::<f32>::deserialize(&bytes).unwrap();
-    sketch.update(2_563.0);
+    let mut sketch = ReqSketch::<ReqF32>::deserialize(&bytes).unwrap();
+    sketch.update(req_f32(2_563.0));
     assert_that!(
-        ReqSketch::<f32>::deserialize(&sketch.serialize()),
+        ReqSketch::<ReqF32>::deserialize(&sketch.serialize()),
         ok(anything())
     );
 }
@@ -388,10 +400,10 @@ fn deserialize_accepts_opaque_compactor_state() {
     bytes[compactor + SECTION_SIZE_RAW_OFFSET..compactor + 
SECTION_SIZE_RAW_OFFSET + 4]
         .copy_from_slice(&raw.to_le_bytes());
     bytes[compactor + NUM_SECTIONS_OFFSET] = 12;
-    let mut sketch = ReqSketch::<f32>::deserialize(&bytes).unwrap();
-    sketch.update(1_001.0);
+    let mut sketch = ReqSketch::<ReqF32>::deserialize(&bytes).unwrap();
+    sketch.update(req_f32(1_001.0));
     assert_that!(
-        ReqSketch::<f32>::deserialize(&sketch.serialize()),
+        ReqSketch::<ReqF32>::deserialize(&sketch.serialize()),
         ok(anything())
     );
 }
@@ -414,7 +426,7 @@ fn 
complementary_compactor_states_do_not_overflow_on_merge() {
         bytes[compactor + SECTION_SIZE_RAW_OFFSET..compactor + 
SECTION_SIZE_RAW_OFFSET + 4]
             .copy_from_slice(&raw.to_le_bytes());
         bytes[compactor + NUM_SECTIONS_OFFSET] = 48;
-        ReqSketch::<f32>::deserialize(&bytes).unwrap()
+        ReqSketch::<ReqF32>::deserialize(&bytes).unwrap()
     });
     let (left, right) = sketches.split_at_mut(1);
     left[0].merge(&right[0]).unwrap();
@@ -424,12 +436,14 @@ fn 
complementary_compactor_states_do_not_overflow_on_merge() {
 fn deserialize_normalizes_false_sorted_claim_and_rejects_nan() {
     let mut unsorted = exact_image(12, &[3.0, 4.0, 5.0, 1.0, 2.0]);
     unsorted[3] |= FLAG_LEVEL_ZERO_SORTED;
-    let sketch = ReqSketch::<f32>::deserialize(&unsorted).unwrap();
+    let sketch = ReqSketch::<ReqF32>::deserialize(&unsorted).unwrap();
     assert_eq!(
-        sketch.rank(&2.0, SearchCriteria::Inclusive).unwrap(),
+        sketch
+            .rank(&req_f32(2.0), SearchCriteria::Inclusive)
+            .unwrap(),
         sketch
             .sorted_view()
-            .rank(&2.0, SearchCriteria::Inclusive)
+            .rank(&req_f32(2.0), SearchCriteria::Inclusive)
             .unwrap(),
     );
 
@@ -475,7 +489,7 @@ fn deserialize_rejects_oversized_compactor_num_items() {
 fn validate_cross_language_fixture(path: PathBuf, expected_n: u64) {
     let bytes =
         fs::read(&path).unwrap_or_else(|e| panic!("failed to read {}: {e}", 
path.display()));
-    let sketch = ReqSketch::<f32>::deserialize(&bytes)
+    let sketch = ReqSketch::<ReqF32>::deserialize(&bytes)
         .unwrap_or_else(|e| panic!("deserialize failed for {}: {e}", 
path.display()));
 
     assert_eq!(sketch.n(), expected_n, "n mismatch on {}", path.display());
@@ -483,8 +497,14 @@ fn validate_cross_language_fixture(path: PathBuf, 
expected_n: u64) {
     assert_eq!(sketch.rank_accuracy(), RankAccuracy::HighRank);
 
     if expected_n > 0 {
-        assert_eq!(sketch.min_item().copied(), Some(1.0_f32));
-        assert_eq!(sketch.max_item().copied(), Some(expected_n as f32));
+        assert_eq!(
+            sketch.min_item().copied().map(ReqFloat::into_inner),
+            Some(1.0)
+        );
+        assert_eq!(
+            sketch.max_item().copied().map(ReqFloat::into_inner),
+            Some(expected_n as f32)
+        );
         let _ = sketch.quantile(0.5, SearchCriteria::Inclusive).unwrap();
     }
 


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to