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::Int(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::Uint(value) => value.try_to_decimal(),
73 Value::Uint128(value) => value.try_to_decimal(),
74 Value::UintBig(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::Int(value) if (-F64_SAFE_I64..=F64_SAFE_I64).contains(value) => Some(*value as f64),
94 Value::Int128(value) if (-F64_SAFE_I128..=F64_SAFE_I128).contains(&value.get()) => {
95 Some(value.get() as f64)
96 }
97 Value::IntBig(value) => value.to_i128().and_then(|integer| {
98 (-F64_SAFE_I128..=F64_SAFE_I128)
99 .contains(&integer)
100 .then_some(integer as f64)
101 }),
102 Value::Timestamp(value) if (-F64_SAFE_I64..=F64_SAFE_I64).contains(&value.repr()) => {
103 Some(value.repr() as f64)
104 }
105 Value::Uint(value) if *value <= F64_SAFE_U64 => Some(*value as f64),
106 Value::Uint128(value) if value.get() <= F64_SAFE_U128 => Some(value.get() as f64),
107 Value::UintBig(value) => value
108 .to_u128()
109 .and_then(|integer| (integer <= F64_SAFE_U128).then_some(integer as f64)),
110
111 _ => None,
112 }
113}
114
115#[must_use]
117pub fn cmp_numeric(left: &Value, right: &Value) -> Option<Ordering> {
118 if !semantics::supports_numeric_coercion(left) || !semantics::supports_numeric_coercion(right) {
119 return None;
120 }
121
122 match (numeric_repr(left), numeric_repr(right)) {
123 (NumericRepr::Decimal(left), NumericRepr::Decimal(right)) => left.partial_cmp(&right),
124 (NumericRepr::F64(left), NumericRepr::F64(right)) => left.partial_cmp(&right),
125 _ => None,
126 }
127}
128
129#[must_use]
131pub(crate) fn compare_decimal_order(left: &Value, right: &Value) -> Option<Ordering> {
132 if !semantics::supports_numeric_coercion(left) || !semantics::supports_numeric_coercion(right) {
133 return None;
134 }
135
136 let left = to_decimal(left)?;
137 let right = to_decimal(right)?;
138
139 left.partial_cmp(&right)
140}
141
142pub(crate) fn add(left: &Value, right: &Value) -> Result<Option<Decimal>, NumericArithmeticError> {
144 apply_decimal_arithmetic(left, right, Decimal::checked_add, false)
145}
146
147pub(crate) fn sub(left: &Value, right: &Value) -> Result<Option<Decimal>, NumericArithmeticError> {
149 apply_decimal_arithmetic(left, right, Decimal::checked_sub, false)
150}
151
152pub(crate) fn mul(left: &Value, right: &Value) -> Result<Option<Decimal>, NumericArithmeticError> {
154 apply_decimal_arithmetic(left, right, Decimal::checked_mul, false)
155}
156
157pub(crate) fn div(left: &Value, right: &Value) -> Result<Option<Decimal>, NumericArithmeticError> {
159 apply_decimal_arithmetic(left, right, Decimal::checked_div, true)
160}
161
162fn apply_decimal_arithmetic(
163 left: &Value,
164 right: &Value,
165 apply: impl FnOnce(Decimal, Decimal) -> Option<Decimal>,
166 division: bool,
167) -> Result<Option<Decimal>, NumericArithmeticError> {
168 if !semantics::supports_numeric_coercion(left) || !semantics::supports_numeric_coercion(right) {
169 return Ok(None);
170 }
171
172 let Some(left) = to_decimal(left) else {
173 return Ok(None);
174 };
175 let Some(right) = to_decimal(right) else {
176 return Ok(None);
177 };
178 if division && right.is_zero() {
179 return Err(NumericArithmeticError::NotRepresentable);
180 }
181
182 apply(left, right)
183 .map(Some)
184 .ok_or(NumericArithmeticError::Overflow)
185}
186
187impl Value {
188 pub(crate) fn to_numeric_decimal(&self) -> Option<Decimal> {
190 to_numeric_decimal(self)
191 }
192
193 #[must_use]
199 pub fn cmp_numeric(&self, other: &Self) -> Option<Ordering> {
200 cmp_numeric(self, other)
201 }
202}