Skip to main content

surrealdb_types/value/
number.rs

1use std::cmp::Ordering;
2use std::fmt::{self, Display};
3use std::hash;
4
5use rust_decimal::Decimal;
6use rust_decimal::prelude::ToPrimitive;
7use serde::{Deserialize, Serialize};
8
9use crate::Kind;
10use crate::sql::{SqlFormat, ToSql};
11
12/// Represents a numeric value in SurrealDB
13///
14/// Numbers in SurrealDB can be integers, floating-point numbers, or decimal numbers.
15/// This enum provides type-safe representation for all numeric types.
16
17#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
18#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
19pub enum Number {
20	/// A 64-bit signed integer
21	Int(i64),
22	/// A 64-bit floating-point number
23	Float(f64),
24	/// A decimal number with arbitrary precision
25	Decimal(Decimal),
26}
27
28impl Number {
29	/// A NaN number
30	pub const NAN: Self = Self::Float(f64::NAN);
31
32	/// Checks if this number is NaN.
33	pub fn is_nan(&self) -> bool {
34		matches!(self, Number::Float(v) if v.is_nan())
35	}
36
37	/// Converts this number into an i64.
38	///
39	/// Returns 0 if the number cannot be converted.
40	pub fn to_int(&self) -> Option<i64> {
41		match self {
42			Number::Int(v) => Some(*v),
43			Number::Float(v) => Some(*v as i64),
44			Number::Decimal(v) => v.to_i64(),
45		}
46	}
47
48	/// Converts this number into an f64.
49	///
50	/// Returns 0.0 if the number cannot be converted.
51	pub fn to_f64(&self) -> Option<f64> {
52		match self {
53			Number::Int(v) => Some(*v as f64),
54			Number::Float(v) => Some(*v),
55			Number::Decimal(v) => v.to_f64(),
56		}
57	}
58}
59
60impl Display for Number {
61	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
62		match self {
63			Number::Int(v) => Display::fmt(v, f),
64			Number::Float(v) => {
65				if v.is_finite() {
66					// Add suffix to distinguish between int and float
67					write!(f, "{v}f")
68				} else {
69					// Don't add suffix for NaN, inf, -inf
70					Display::fmt(v, f)
71				}
72			}
73			Number::Decimal(v) => write!(f, "{v}dec"),
74		}
75	}
76}
77
78impl ToSql for Number {
79	fn fmt_sql(&self, f: &mut String, fmt: SqlFormat) {
80		match self {
81			Number::Int(v) => f.push_str(&v.to_string()),
82			Number::Float(v) => {
83				if v.is_finite() {
84					f.push_str(&v.to_string());
85					f.push('f');
86				} else {
87					// NaN, inf, -inf
88					f.push_str(&v.to_string());
89				}
90			}
91			Number::Decimal(v) => v.fmt_sql(f, fmt),
92		}
93	}
94}
95
96macro_rules! impl_number {
97	($($variant:ident($type:ty) => ($is:ident, $into:ident, $from:ident),)+) => {
98		impl Number {
99			/// Get the kind of number
100			pub fn kind(&self) -> Kind {
101				match self {
102					$(
103						Self::$variant(_) => Kind::$variant,
104					)+
105				}
106			}
107
108			$(
109				/// Check if this is a of the given type
110				pub fn $is(&self) -> bool {
111					matches!(self, Self::$variant(_))
112				}
113
114				/// Convert this number into the given type
115				pub fn $into(self) -> anyhow::Result<$type> {
116					if let Self::$variant(v) = self {
117						Ok(v)
118					} else {
119						Err(anyhow::anyhow!("Expected a {} but got a {}", Kind::$variant, self.kind()))
120					}
121				}
122
123				/// Create a new number from the given type
124				pub fn $from(v: $type) -> Self {
125					Self::$variant(v)
126				}
127			)+
128		}
129	}
130}
131
132impl_number! (
133	Int(i64) => (is_int, into_int, from_int),
134	Float(f64) => (is_float, into_float, from_float),
135	Decimal(Decimal) => (is_decimal, into_decimal, from_decimal),
136);
137
138impl Default for Number {
139	fn default() -> Self {
140		Self::Int(0)
141	}
142}
143
144impl Eq for Number {}
145
146impl Ord for Number {
147	fn cmp(&self, other: &Self) -> Ordering {
148		fn total_cmp_f64(a: f64, b: f64) -> Ordering {
149			if a == 0.0 && b == 0.0 {
150				// -0.0 = 0.0
151				Ordering::Equal
152			} else {
153				// Handles NaN's
154				a.total_cmp(&b)
155			}
156		}
157
158		// Pick the greater number depending on whether it's positive.
159		macro_rules! greater {
160			($f:ident) => {
161				if $f.is_sign_positive() {
162					Ordering::Greater
163				} else {
164					Ordering::Less
165				}
166			};
167		}
168
169		match (self, other) {
170			(Number::Int(v), Number::Int(w)) => v.cmp(w),
171			(Number::Float(v), Number::Float(w)) => total_cmp_f64(*v, *w),
172			(Number::Decimal(v), Number::Decimal(w)) => v.cmp(w),
173			// ------------------------------
174			(Number::Int(v), Number::Float(w)) => {
175				// If the float is not finite, we don't need to compare it to the integer.
176				if !w.is_finite() {
177					return greater!(w).reverse();
178				}
179				// Cast int to i128 to avoid saturating.
180				let l = *v as i128;
181				// Cast the integer-part of the float to i128 to avoid saturating.
182				let r = *w as i128;
183				// Compare both integer parts.
184				match l.cmp(&r) {
185					// If the integer parts are equal then we need to compare the mantissa.
186					Ordering::Equal => total_cmp_f64(0.0, w.fract()),
187					// If the integer parts are not equal then we already know the correct ordering.
188					ordering => ordering,
189				}
190			}
191			(v @ Number::Float(_), w @ Number::Int(_)) => w.cmp(v).reverse(),
192			// ------------------------------
193			(Number::Int(v), Number::Decimal(w)) => Decimal::from(*v).cmp(w),
194			(Number::Decimal(v), Number::Int(w)) => v.cmp(&Decimal::from(*w)),
195			// ------------------------------
196			(Number::Float(v), Number::Decimal(w)) => {
197				// Compare fractional parts of the float and decimal.
198				macro_rules! compare_fractions {
199					($l:ident, $r:ident) => {
200						match ($l == 0.0, $r == Decimal::ZERO) {
201							// If both numbers are zero, these are equal.
202							(true, true) => {
203								return Ordering::Equal;
204							}
205							// If only the float is zero, check the decimal's sign.
206							(true, false) => {
207								return greater!($r).reverse();
208							}
209							// If only the decimal is zero, check the float's sign.
210							(false, true) => {
211								return greater!($l);
212							}
213							// If neither is zero, continue checking the rest of the digits.
214							(false, false) => {
215								continue;
216							}
217						}
218					};
219				}
220				// If the float is not finite, we don't need to compare it to the decimal
221				if !v.is_finite() {
222					return greater!(v);
223				}
224				// Cast int to i128 to avoid saturating.
225				let l = *v as i128;
226				// Cast the integer-part of the decimal to i128.
227				let Ok(r) = i128::try_from(*w) else {
228					return greater!(w).reverse();
229				};
230				// Compare both integer parts.
231				match l.cmp(&r) {
232					// If the integer parts are equal then we need to compare the fractional parts.
233					Ordering::Equal => {
234						// We can't compare the fractional parts of floats with decimals reliably.
235						// Instead, we need to compare them as integers. To do this, we need to
236						// multiply the fraction with a number large enough to move some digits
237						// to the integer part of the float or decimal. The number should fit in
238						// 52 bits and be able to multiply f64 fractions between -1 and 1 without
239						// losing precision. Since we may need to do this repeatedly it helps if
240						// the number is as big as possible to reduce the number of
241						// iterations needed.
242						//
243						// This number is roughly 2 ^ 53 with the last digits truncated in order
244						// to make sure the fraction converges to 0 every time we multiply it.
245						// This is a magic number I found through my experiments so don't ask me
246						// the logic behind it :) Before changing this number, please make sure
247						// that the relevant tests aren't flaky after changing it.
248						const SAFE_MULTIPLIER: i64 = 9_007_199_254_740_000;
249						// Get the fractional part of the float.
250						let mut l = v.fract();
251						// Get the fractional part of the decimal.
252						let mut r = w.fract();
253						// Move the digits and compare them.
254						// This is very generous. For example, for our tests to pass we only need
255						// 3 iterations. This should be at least 6 to make sure we cover all
256						// possible decimals and floats.
257						for _ in 0..12 {
258							l *= SAFE_MULTIPLIER as f64;
259							r *= Decimal::new(SAFE_MULTIPLIER, 0);
260							// Cast the integer part of the decimal to i64. The fractions are always
261							// less than 1 so we know this will always be less than SAFE_MULTIPLIER.
262							match r.to_i64() {
263								Some(ref right) => match (l as i64).cmp(right) {
264									// If the integer parts are equal, we need to check the
265									// remaining fractional parts.
266									Ordering::Equal => {
267										// Drop the integer parts we already compared.
268										l = l.fract();
269										r = r.fract();
270										// Compare the fractional parts and decide whether to return
271										// or continue checking the next digits.
272										compare_fractions!(l, r);
273									}
274									ordering => {
275										// If the integer parts are not equal then we already know
276										// the correct ordering.
277										return ordering;
278									}
279								},
280								// This is technically unreachable. Reaching this part likely
281								// indicates a bug in `rust-decimal`'s `to_f64`'s
282								// implementation.
283								None => {
284									// We will assume the decimal is bigger or smaller depending on
285									// its sign.
286									return greater!(w).reverse();
287								}
288							}
289						}
290						// After our iterations, if we still haven't exhausted both fractions we
291						// will just treat them as equal. It should be impossible to reach
292						// this point after at least 6 iterations. We could use an infinite
293						// loop instead but this way we make sure the loop always exits.
294						Ordering::Equal
295					}
296					// If the integer parts are not equal then we already know the correct ordering.
297					ordering => ordering,
298				}
299			}
300			(v @ Number::Decimal(..), w @ Number::Float(..)) => w.cmp(v).reverse(),
301		}
302	}
303}
304
305// Warning: Equal numbers may have different hashes, which violates
306// the invariants of certain collections!
307impl hash::Hash for Number {
308	fn hash<H: hash::Hasher>(&self, state: &mut H) {
309		match self {
310			Number::Int(v) => v.hash(state),
311			Number::Float(v) => v.to_bits().hash(state),
312			Number::Decimal(v) => v.hash(state),
313		}
314	}
315}
316
317impl PartialEq for Number {
318	fn eq(&self, other: &Self) -> bool {
319		fn total_eq_f64(a: f64, b: f64) -> bool {
320			a.to_bits().eq(&b.to_bits()) || (a == 0.0 && b == 0.0)
321		}
322
323		match (self, other) {
324			(Number::Int(v), Number::Int(w)) => v.eq(w),
325			(Number::Float(v), Number::Float(w)) => total_eq_f64(*v, *w),
326			(Number::Decimal(v), Number::Decimal(w)) => v.eq(w),
327			// ------------------------------
328			(v @ Number::Int(_), w @ Number::Float(_)) => v.cmp(w) == Ordering::Equal,
329			(v @ Number::Float(_), w @ Number::Int(_)) => v.cmp(w) == Ordering::Equal,
330			// ------------------------------
331			(Number::Int(v), Number::Decimal(w)) => Decimal::from(*v).eq(w),
332			(Number::Decimal(v), Number::Int(w)) => v.eq(&Decimal::from(*w)),
333			// ------------------------------
334			(v @ Number::Float(_), w @ Number::Decimal(_)) => v.cmp(w) == Ordering::Equal,
335			(v @ Number::Decimal(_), w @ Number::Float(_)) => v.cmp(w) == Ordering::Equal,
336		}
337	}
338}
339
340impl PartialOrd for Number {
341	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
342		Some(self.cmp(other))
343	}
344}
345
346// From implementations for common numeric types
347impl From<i32> for Number {
348	fn from(value: i32) -> Self {
349		Number::Int(value as i64)
350	}
351}
352
353impl From<i64> for Number {
354	fn from(value: i64) -> Self {
355		Number::Int(value)
356	}
357}
358
359impl From<f32> for Number {
360	fn from(value: f32) -> Self {
361		Number::Float(value as f64)
362	}
363}
364
365impl From<f64> for Number {
366	fn from(value: f64) -> Self {
367		Number::Float(value)
368	}
369}
370
371impl From<Decimal> for Number {
372	fn from(value: Decimal) -> Self {
373		Number::Decimal(value)
374	}
375}