Skip to main content

reifydb_value/value/
ordered_f64.rs

1// SPDX-License-Identifier: MIT
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				Ok(OrderedF64(value))
48			}
49
50			fn visit_f32<E>(self, value: f32) -> Result<Self::Value, E>
51			where
52				E: de::Error,
53			{
54				Ok(OrderedF64(value as f64))
55			}
56		}
57
58		deserializer.deserialize_f64(F64Visitor)
59	}
60}
61
62impl OrderedF64 {
63	pub fn value(&self) -> f64 {
64		self.0
65	}
66
67	pub fn zero() -> OrderedF64 {
68		OrderedF64(0.0f64)
69	}
70}
71
72impl Deref for OrderedF64 {
73	type Target = f64;
74
75	fn deref(&self) -> &Self::Target {
76		&self.0
77	}
78}
79
80impl Display for OrderedF64 {
81	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
82		f.write_str(&format_f64(self.0))
83	}
84}
85
86impl PartialEq for OrderedF64 {
87	fn eq(&self, other: &Self) -> bool {
88		self.0.to_bits() == other.0.to_bits()
89	}
90}
91
92impl Eq for OrderedF64 {}
93
94impl PartialOrd for OrderedF64 {
95	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
96		Some(self.cmp(other))
97	}
98}
99
100impl Ord for OrderedF64 {
101	fn cmp(&self, other: &Self) -> Ordering {
102		let l = float_to_ordered_u64(self.0);
103		let r = float_to_ordered_u64(other.0);
104		l.cmp(&r)
105	}
106}
107
108#[inline]
109fn float_to_ordered_u64(f: f64) -> u64 {
110	let bits = f.to_bits();
111	if bits & 0x8000000000000000 == 0 {
112		bits ^ 0x8000000000000000
113	} else {
114		!bits
115	}
116}
117
118impl Hash for OrderedF64 {
119	fn hash<H: Hasher>(&self, state: &mut H) {
120		self.0.to_bits().hash(state);
121	}
122}
123
124impl From<OrderedF64> for f64 {
125	fn from(v: OrderedF64) -> Self {
126		v.0
127	}
128}
129
130impl TryFrom<f64> for OrderedF64 {
131	type Error = Error;
132
133	fn try_from(f: f64) -> Result<Self, Self::Error> {
134		let normalized = if f == 0.0 {
135			0.0
136		} else {
137			f
138		};
139		if f.is_nan() {
140			Err(TypeError::NanNotAllowed.into())
141		} else {
142			Ok(OrderedF64(normalized))
143		}
144	}
145}
146
147#[cfg(test)]
148#[allow(clippy::approx_constant)]
149pub mod tests {
150	use std::{collections::HashSet, convert::TryFrom};
151
152	use super::*;
153
154	#[test]
155	fn test_eq_and_ord() {
156		let a = OrderedF64::try_from(3.14).unwrap();
157		let b = OrderedF64::try_from(3.14).unwrap();
158		let c = OrderedF64::try_from(2.71).unwrap();
159
160		assert_eq!(a, b);
161		assert!(a > c);
162		assert!(c < a);
163	}
164
165	#[test]
166	fn test_sorting() {
167		let mut values = vec![
168			OrderedF64::try_from(10.0).unwrap(),
169			OrderedF64::try_from(2.0).unwrap(),
170			OrderedF64::try_from(5.0).unwrap(),
171		];
172		values.sort();
173		let sorted: Vec<f64> = values.into_iter().map(|v| v.0).collect();
174		assert_eq!(sorted, vec![2.0, 5.0, 10.0]);
175	}
176
177	#[test]
178	fn test_hash_eq() {
179		let a = OrderedF64::try_from(1.0).unwrap();
180		let b = OrderedF64::try_from(1.0).unwrap();
181
182		let mut set = HashSet::new();
183		set.insert(a);
184		assert!(set.contains(&b));
185	}
186
187	#[test]
188	fn test_normalizes_zero() {
189		let pos_zero = OrderedF64::try_from(0.0).unwrap();
190		let neg_zero = OrderedF64::try_from(-0.0).unwrap();
191
192		assert_eq!(pos_zero, neg_zero);
193
194		let mut set = HashSet::new();
195		set.insert(pos_zero);
196		assert!(set.contains(&neg_zero));
197	}
198
199	#[test]
200	fn test_nan_fails() {
201		assert!(OrderedF64::try_from(f64::NAN).is_err());
202	}
203
204	#[test]
205	fn test_negative_less_than_positive() {
206		let neg = OrderedF64::try_from(-1.5).unwrap();
207		let pos = OrderedF64::try_from(1.5).unwrap();
208		assert!(neg < pos);
209	}
210
211	#[test]
212	fn test_negative_less_than_zero() {
213		let neg = OrderedF64::try_from(-0.0001).unwrap();
214		let zero = OrderedF64::try_from(0.0).unwrap();
215		assert!(neg < zero);
216	}
217
218	#[test]
219	fn test_sorting_with_negatives() {
220		let mut values = vec![
221			OrderedF64::try_from(3.14).unwrap(),
222			OrderedF64::try_from(-1.5).unwrap(),
223			OrderedF64::try_from(0.0).unwrap(),
224			OrderedF64::try_from(99999.0).unwrap(),
225			OrderedF64::try_from(-100.0).unwrap(),
226		];
227		values.sort();
228		let sorted: Vec<f64> = values.into_iter().map(|v| v.0).collect();
229		assert_eq!(sorted, vec![-100.0, -1.5, 0.0, 3.14, 99999.0]);
230	}
231}