icydb_core/value/ops/
numeric.rs1use crate::{
8 traits::{NumericValue, Repr},
9 types::Decimal,
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
33#[derive(Clone, Copy, Debug, Eq, PartialEq)]
42pub(crate) enum NumericArithmeticError {
43 Overflow,
44 NotRepresentable,
45}
46
47fn numeric_repr(value: &Value) -> NumericRepr {
48 if !semantics::supports_numeric_coercion(value) {
50 return NumericRepr::None;
51 }
52
53 if let Some(decimal) = to_decimal(value) {
54 return NumericRepr::Decimal(decimal);
55 }
56 if let Some(float) = to_f64_lossless(value) {
57 return NumericRepr::F64(float);
58 }
59 NumericRepr::None
60}
61
62fn to_decimal(value: &Value) -> Option<Decimal> {
63 match value {
64 Value::Decimal(value) => value.try_to_decimal(),
65 Value::Duration(value) => value.try_to_decimal(),
66 Value::Float64(value) => value.try_to_decimal(),
67 Value::Float32(value) => value.try_to_decimal(),
68 Value::Int64(value) => value.try_to_decimal(),
69 Value::Int128(value) => value.try_to_decimal(),
70 Value::IntBig(value) => value.try_to_decimal(),
71 Value::Timestamp(value) => value.try_to_decimal(),
72 Value::Nat64(value) => value.try_to_decimal(),
73 Value::Nat128(value) => value.try_to_decimal(),
74 Value::NatBig(value) => value.try_to_decimal(),
75
76 _ => None,
77 }
78}
79
80pub(crate) fn to_numeric_decimal(value: &Value) -> Option<Decimal> {
82 to_decimal(value)
83}
84
85#[expect(clippy::cast_precision_loss)]
88fn to_f64_lossless(value: &Value) -> Option<f64> {
89 match value {
90 Value::Duration(value) if value.repr() <= F64_SAFE_U64 => Some(value.repr() as f64),
91 Value::Float64(value) => Some(value.get()),
92 Value::Float32(value) => Some(f64::from(value.get())),
93 Value::Int64(value) if (-F64_SAFE_I64..=F64_SAFE_I64).contains(value) => {
94 Some(*value as f64)
95 }
96 Value::Int128(value) if (-F64_SAFE_I128..=F64_SAFE_I128).contains(value) => {
97 Some(*value as f64)
98 }
99 Value::IntBig(value) => value.to_i128().and_then(|integer| {
100 (-F64_SAFE_I128..=F64_SAFE_I128)
101 .contains(&integer)
102 .then_some(integer as f64)
103 }),
104 Value::Timestamp(value) if (-F64_SAFE_I64..=F64_SAFE_I64).contains(&value.repr()) => {
105 Some(value.repr() as f64)
106 }
107 Value::Nat64(value) if *value <= F64_SAFE_U64 => Some(*value as f64),
108 Value::Nat128(value) if *value <= F64_SAFE_U128 => Some(*value as f64),
109 Value::NatBig(value) => value
110 .to_u128()
111 .and_then(|integer| (integer <= F64_SAFE_U128).then_some(integer as f64)),
112
113 _ => None,
114 }
115}
116
117#[must_use]
119fn cmp_numeric(left: &Value, right: &Value) -> Option<Ordering> {
120 if !semantics::supports_numeric_coercion(left) || !semantics::supports_numeric_coercion(right) {
121 return None;
122 }
123
124 match (numeric_repr(left), numeric_repr(right)) {
125 (NumericRepr::Decimal(left), NumericRepr::Decimal(right)) => left.partial_cmp(&right),
126 (NumericRepr::F64(left), NumericRepr::F64(right)) => left.partial_cmp(&right),
127 _ => None,
128 }
129}
130
131#[must_use]
133pub(crate) fn compare_decimal_order(left: &Value, right: &Value) -> Option<Ordering> {
134 if !semantics::supports_numeric_coercion(left) || !semantics::supports_numeric_coercion(right) {
135 return None;
136 }
137
138 let left = to_decimal(left)?;
139 let right = to_decimal(right)?;
140
141 left.partial_cmp(&right)
142}
143
144pub(crate) fn add(left: &Value, right: &Value) -> Result<Option<Decimal>, NumericArithmeticError> {
146 apply_decimal_arithmetic(left, right, Decimal::checked_add, false)
147}
148
149pub(crate) fn sub(left: &Value, right: &Value) -> Result<Option<Decimal>, NumericArithmeticError> {
151 apply_decimal_arithmetic(left, right, Decimal::checked_sub, false)
152}
153
154pub(crate) fn mul(left: &Value, right: &Value) -> Result<Option<Decimal>, NumericArithmeticError> {
156 apply_decimal_arithmetic(left, right, Decimal::checked_mul, false)
157}
158
159pub(crate) fn div(left: &Value, right: &Value) -> Result<Option<Decimal>, NumericArithmeticError> {
161 apply_decimal_arithmetic(left, right, Decimal::checked_div, true)
162}
163
164fn apply_decimal_arithmetic(
165 left: &Value,
166 right: &Value,
167 apply: impl FnOnce(Decimal, Decimal) -> Option<Decimal>,
168 division: bool,
169) -> Result<Option<Decimal>, NumericArithmeticError> {
170 if !semantics::supports_numeric_coercion(left) || !semantics::supports_numeric_coercion(right) {
171 return Ok(None);
172 }
173
174 let Some(left) = to_decimal(left) else {
175 return Ok(None);
176 };
177 let Some(right) = to_decimal(right) else {
178 return Ok(None);
179 };
180 if division && right.is_zero() {
181 return Err(NumericArithmeticError::NotRepresentable);
182 }
183
184 apply(left, right)
185 .map(Some)
186 .ok_or(NumericArithmeticError::Overflow)
187}
188
189impl Value {
190 pub(crate) fn to_numeric_decimal(&self) -> Option<Decimal> {
192 to_numeric_decimal(self)
193 }
194
195 #[must_use]
201 pub fn cmp_numeric(&self, other: &Self) -> Option<Ordering> {
202 cmp_numeric(self, other)
203 }
204}