icydb_core/value/ops/
numeric.rs1use crate::{
8 traits::Repr,
9 types::{Decimal, NumericValue},
10 value::{Value, semantics},
11};
12use std::cmp::Ordering;
13
14const F64_SAFE_I64: i64 = 1i64 << 53;
15const F64_SAFE_U64: u64 = 1u64 << 53;
16const F64_SAFE_I128: i128 = 1i128 << 53;
17const F64_SAFE_U128: u128 = 1u128 << 53;
18
19enum NumericRepr {
28 Decimal(Decimal),
29 F64(f64),
30 None,
31}
32
33fn numeric_repr(value: &Value) -> NumericRepr {
34 if !semantics::supports_numeric_coercion(value) {
36 return NumericRepr::None;
37 }
38
39 if let Some(decimal) = to_decimal(value) {
40 return NumericRepr::Decimal(decimal);
41 }
42 if let Some(float) = to_f64_lossless(value) {
43 return NumericRepr::F64(float);
44 }
45 NumericRepr::None
46}
47
48fn to_decimal(value: &Value) -> Option<Decimal> {
49 match value {
50 Value::Decimal(value) => value.try_to_decimal(),
51 Value::Duration(value) => value.try_to_decimal(),
52 Value::Float64(value) => value.try_to_decimal(),
53 Value::Float32(value) => value.try_to_decimal(),
54 Value::Int64(value) => value.try_to_decimal(),
55 Value::Int128(value) => value.try_to_decimal(),
56 Value::IntBig(value) => value.try_to_decimal(),
57 Value::Timestamp(value) => value.try_to_decimal(),
58 Value::Nat64(value) => value.try_to_decimal(),
59 Value::Nat128(value) => value.try_to_decimal(),
60 Value::NatBig(value) => value.try_to_decimal(),
61
62 _ => None,
63 }
64}
65
66pub(crate) fn to_numeric_decimal(value: &Value) -> Option<Decimal> {
68 to_decimal(value)
69}
70
71#[expect(clippy::cast_precision_loss)]
74fn to_f64_lossless(value: &Value) -> Option<f64> {
75 match value {
76 Value::Duration(value) if value.repr() <= F64_SAFE_U64 => Some(value.repr() as f64),
77 Value::Float64(value) => Some(value.get()),
78 Value::Float32(value) => Some(f64::from(value.get())),
79 Value::Int64(value) if (-F64_SAFE_I64..=F64_SAFE_I64).contains(value) => {
80 Some(*value as f64)
81 }
82 Value::Int128(value) if (-F64_SAFE_I128..=F64_SAFE_I128).contains(value) => {
83 Some(*value as f64)
84 }
85 Value::IntBig(value) => value.to_i128().and_then(|integer| {
86 (-F64_SAFE_I128..=F64_SAFE_I128)
87 .contains(&integer)
88 .then_some(integer as f64)
89 }),
90 Value::Timestamp(value) if (-F64_SAFE_I64..=F64_SAFE_I64).contains(&value.repr()) => {
91 Some(value.repr() as f64)
92 }
93 Value::Nat64(value) if *value <= F64_SAFE_U64 => Some(*value as f64),
94 Value::Nat128(value) if *value <= F64_SAFE_U128 => Some(*value as f64),
95 Value::NatBig(value) => value
96 .to_u128()
97 .and_then(|integer| (integer <= F64_SAFE_U128).then_some(integer as f64)),
98
99 _ => None,
100 }
101}
102
103#[must_use]
105fn cmp_numeric(left: &Value, right: &Value) -> Option<Ordering> {
106 if !semantics::supports_numeric_coercion(left) || !semantics::supports_numeric_coercion(right) {
107 return None;
108 }
109
110 match (numeric_repr(left), numeric_repr(right)) {
111 (NumericRepr::Decimal(left), NumericRepr::Decimal(right)) => left.partial_cmp(&right),
112 (NumericRepr::F64(left), NumericRepr::F64(right)) => left.partial_cmp(&right),
113 _ => None,
114 }
115}
116
117#[must_use]
119pub(crate) fn compare_decimal_order(left: &Value, right: &Value) -> Option<Ordering> {
120 if !semantics::supports_numeric_coercion(left) || !semantics::supports_numeric_coercion(right) {
121 return None;
122 }
123
124 let left = to_decimal(left)?;
125 let right = to_decimal(right)?;
126
127 left.partial_cmp(&right)
128}
129
130impl Value {
131 pub(crate) fn to_numeric_decimal(&self) -> Option<Decimal> {
133 to_numeric_decimal(self)
134 }
135
136 #[must_use]
142 pub fn cmp_numeric(&self, other: &Self) -> Option<Ordering> {
143 cmp_numeric(self, other)
144 }
145}