Skip to main content

reifydb_value/value/
ordered_f64.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::{
5	cmp::Ordering,
6	fmt,
7	fmt::{Display, Formatter},
8	hash::{Hash, Hasher},
9	ops::Deref,
10};
11
12use serde::{Deserialize, Deserializer, Serialize, Serializer, de, de::Visitor};
13
14use crate::{
15	error::{Error, TypeError},
16	util::float_format::format_f64,
17};
18
19#[repr(transparent)]
20#[derive(Debug, Copy, Clone, Default)]
21pub struct OrderedF64(f64);
22
23impl Serialize for OrderedF64 {
24	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
25	where
26		S: Serializer,
27	{
28		serializer.serialize_f64(self.0)
29	}
30}
31
32impl<'de> Deserialize<'de> for OrderedF64 {
33	fn deserialize<D>(deserializer: D) -> Result<OrderedF64, D::Error>
34	where
35		D: Deserializer<'de>,
36	{
37		struct F64Visitor;
38
39		impl Visitor<'_> for F64Visitor {
40			type Value = OrderedF64;
41
42			fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
43				formatter.write_str("a 64-bit floating point number")
44			}
45
46			fn visit_f64<E>(self, value: f64) -> Result<Self::Value, E>
47			where
48				E: de::Error,
49			{
50				OrderedF64::try_from(value)
51					.map_err(|e| E::custom(format_args!("OrderedF64: {}", e.diagnostic().message)))
52			}
53
54			fn visit_f32<E>(self, value: f32) -> Result<Self::Value, E>
55			where
56				E: de::Error,
57			{
58				OrderedF64::try_from(value as f64)
59					.map_err(|e| E::custom(format_args!("OrderedF64: {}", e.diagnostic().message)))
60			}
61		}
62
63		deserializer.deserialize_f64(F64Visitor)
64	}
65}
66
67impl OrderedF64 {
68	pub fn value(&self) -> f64 {
69		self.0
70	}
71
72	pub fn zero() -> OrderedF64 {
73		OrderedF64(0.0f64)
74	}
75
76	pub fn canonical(f: f64) -> f64 {
77		if f.is_nan() {
78			f64::NAN
79		} else if f == 0.0 {
80			0.0
81		} else {
82			f
83		}
84	}
85}
86
87impl Deref for OrderedF64 {
88	type Target = f64;
89
90	fn deref(&self) -> &Self::Target {
91		&self.0
92	}
93}
94
95impl Display for OrderedF64 {
96	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
97		f.write_str(&format_f64(self.0))
98	}
99}
100
101impl PartialEq for OrderedF64 {
102	fn eq(&self, other: &Self) -> bool {
103		self.0.to_bits() == other.0.to_bits()
104	}
105}
106
107impl Eq for OrderedF64 {}
108
109impl PartialOrd for OrderedF64 {
110	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
111		Some(self.cmp(other))
112	}
113}
114
115impl Ord for OrderedF64 {
116	fn cmp(&self, other: &Self) -> Ordering {
117		let l = float_to_ordered_u64(self.0);
118		let r = float_to_ordered_u64(other.0);
119		l.cmp(&r)
120	}
121}
122
123#[inline]
124fn float_to_ordered_u64(f: f64) -> u64 {
125	let bits = f.to_bits();
126	if bits & 0x8000000000000000 == 0 {
127		bits ^ 0x8000000000000000
128	} else {
129		!bits
130	}
131}
132
133impl Hash for OrderedF64 {
134	fn hash<H: Hasher>(&self, state: &mut H) {
135		self.0.to_bits().hash(state);
136	}
137}
138
139impl From<OrderedF64> for f64 {
140	fn from(v: OrderedF64) -> Self {
141		v.0
142	}
143}
144
145impl TryFrom<f64> for OrderedF64 {
146	type Error = Error;
147
148	fn try_from(f: f64) -> Result<Self, Self::Error> {
149		if f.is_nan() {
150			Err(TypeError::NanNotAllowed.into())
151		} else {
152			Ok(OrderedF64(Self::canonical(f)))
153		}
154	}
155}
156
157#[cfg(test)]
158#[allow(clippy::approx_constant)]
159pub mod tests {
160	use std::{collections::HashSet, convert::TryFrom};
161
162	use super::*;
163
164	#[test]
165	fn test_eq_and_ord() {
166		let a = OrderedF64::try_from(3.14).unwrap();
167		let b = OrderedF64::try_from(3.14).unwrap();
168		let c = OrderedF64::try_from(2.71).unwrap();
169
170		assert_eq!(a, b);
171		assert!(a > c);
172		assert!(c < a);
173	}
174
175	#[test]
176	fn test_sorting() {
177		let mut values = vec![
178			OrderedF64::try_from(10.0).unwrap(),
179			OrderedF64::try_from(2.0).unwrap(),
180			OrderedF64::try_from(5.0).unwrap(),
181		];
182		values.sort();
183		let sorted: Vec<f64> = values.into_iter().map(|v| v.0).collect();
184		assert_eq!(sorted, vec![2.0, 5.0, 10.0]);
185	}
186
187	#[test]
188	fn test_hash_eq() {
189		let a = OrderedF64::try_from(1.0).unwrap();
190		let b = OrderedF64::try_from(1.0).unwrap();
191
192		let mut set = HashSet::new();
193		set.insert(a);
194		assert!(set.contains(&b));
195	}
196
197	#[test]
198	fn test_normalizes_zero() {
199		let pos_zero = OrderedF64::try_from(0.0).unwrap();
200		let neg_zero = OrderedF64::try_from(-0.0).unwrap();
201
202		assert_eq!(pos_zero, neg_zero);
203
204		let mut set = HashSet::new();
205		set.insert(pos_zero);
206		assert!(set.contains(&neg_zero));
207	}
208
209	#[test]
210	fn test_nan_fails() {
211		assert!(OrderedF64::try_from(f64::NAN).is_err());
212	}
213
214	#[test]
215	fn test_negative_less_than_positive() {
216		let neg = OrderedF64::try_from(-1.5).unwrap();
217		let pos = OrderedF64::try_from(1.5).unwrap();
218		assert!(neg < pos);
219	}
220
221	#[test]
222	fn test_negative_less_than_zero() {
223		let neg = OrderedF64::try_from(-0.0001).unwrap();
224		let zero = OrderedF64::try_from(0.0).unwrap();
225		assert!(neg < zero);
226	}
227
228	#[test]
229	fn test_sorting_with_negatives() {
230		let mut values = vec![
231			OrderedF64::try_from(3.14).unwrap(),
232			OrderedF64::try_from(-1.5).unwrap(),
233			OrderedF64::try_from(0.0).unwrap(),
234			OrderedF64::try_from(99999.0).unwrap(),
235			OrderedF64::try_from(-100.0).unwrap(),
236		];
237		values.sort();
238		let sorted: Vec<f64> = values.into_iter().map(|v| v.0).collect();
239		assert_eq!(sorted, vec![-100.0, -1.5, 0.0, 3.14, 99999.0]);
240	}
241}