Skip to main content

icydb_core/value/ops/
numeric.rs

1//! Module: value::ops::numeric
2//!
3//! Responsibility: representation-local numeric conversion and comparison.
4//! Does not own: predicate-level numeric policy or planner coercion legality.
5//! Boundary: low-level helpers consumed by database numeric semantics.
6
7use 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
19///
20/// NumericRepr
21///
22/// Represents the comparable numeric form available for one `Value`. Decimal
23/// is preferred when exact conversion is available; otherwise a lossless `f64`
24/// is used only for values inside the well-defined integer safety envelope.
25///
26
27enum NumericRepr {
28    Decimal(Decimal),
29    F64(f64),
30    None,
31}
32
33fn numeric_repr(value: &Value) -> NumericRepr {
34    // Numeric comparison eligibility is registry-authoritative.
35    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
64// Internal numeric coercion helper for aggregate arithmetic.
65pub(crate) fn to_numeric_decimal(value: &Value) -> Option<Decimal> {
66    to_decimal(value)
67}
68
69// This helper only returns `Some` inside the integer range exactly representable
70// by `f64`, or for finite float wrappers that already own their precision.
71#[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/// Compare two runtime values under value-local numeric coercion semantics.
94#[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/// Compare two values after exact decimal numeric coercion.
108#[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    // Internal numeric coercion helper for aggregate arithmetic.
122    pub(crate) fn to_numeric_decimal(&self) -> Option<Decimal> {
123        to_numeric_decimal(self)
124    }
125
126    /// Compare two runtime values under value-local numeric coercion semantics.
127    ///
128    /// Database execution code should use `db::numeric` helpers as the
129    /// canonical runtime boundary; this method remains the representation-local
130    /// comparison primitive that those higher-level helpers are tested against.
131    #[must_use]
132    pub fn cmp_numeric(&self, other: &Self) -> Option<Ordering> {
133        cmp_numeric(self, other)
134    }
135}