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::Timestamp(value) => value.try_to_decimal(),
57 Value::Nat64(value) => value.try_to_decimal(),
58 Value::Nat128(value) => value.try_to_decimal(),
59
60 _ => None,
61 }
62}
63
64pub(crate) fn to_numeric_decimal(value: &Value) -> Option<Decimal> {
66 to_decimal(value)
67}
68
69#[expect(clippy::cast_precision_loss)]
72fn to_f64_lossless(value: &Value) -> Option<f64> {
73 match value {
74 Value::Duration(value) if value.repr() <= F64_SAFE_U64 => Some(value.repr() as f64),
75 Value::Float64(value) => Some(value.get()),
76 Value::Float32(value) => Some(f64::from(value.get())),
77 Value::Int64(value) if (-F64_SAFE_I64..=F64_SAFE_I64).contains(value) => {
78 Some(*value as f64)
79 }
80 Value::Int128(value) if (-F64_SAFE_I128..=F64_SAFE_I128).contains(value) => {
81 Some(*value as f64)
82 }
83 Value::Timestamp(value) if (-F64_SAFE_I64..=F64_SAFE_I64).contains(&value.repr()) => {
84 Some(value.repr() as f64)
85 }
86 Value::Nat64(value) if *value <= F64_SAFE_U64 => Some(*value as f64),
87 Value::Nat128(value) if *value <= F64_SAFE_U128 => Some(*value as f64),
88
89 _ => None,
90 }
91}
92
93#[must_use]
95fn cmp_numeric(left: &Value, right: &Value) -> Option<Ordering> {
96 if !semantics::supports_numeric_coercion(left) || !semantics::supports_numeric_coercion(right) {
97 return None;
98 }
99
100 match (numeric_repr(left), numeric_repr(right)) {
101 (NumericRepr::Decimal(left), NumericRepr::Decimal(right)) => left.partial_cmp(&right),
102 (NumericRepr::F64(left), NumericRepr::F64(right)) => left.partial_cmp(&right),
103 _ => None,
104 }
105}
106
107#[must_use]
109pub(crate) fn compare_decimal_order(left: &Value, right: &Value) -> Option<Ordering> {
110 if !semantics::supports_numeric_coercion(left) || !semantics::supports_numeric_coercion(right) {
111 return None;
112 }
113
114 let left = to_decimal(left)?;
115 let right = to_decimal(right)?;
116
117 left.partial_cmp(&right)
118}
119
120impl Value {
121 pub(crate) fn to_numeric_decimal(&self) -> Option<Decimal> {
123 to_numeric_decimal(self)
124 }
125
126 #[must_use]
132 pub fn cmp_numeric(&self, other: &Self) -> Option<Ordering> {
133 cmp_numeric(self, other)
134 }
135}