ddsketch/canonical/mapping/
fixed.rs1use datadog_protos::sketches::{index_mapping::Interpolation, IndexMapping as ProtoIndexMapping};
4
5use super::IndexMapping;
6use crate::canonical::error::ProtoConversionError;
7use crate::common::float_eq;
8
9#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
27pub struct FixedLogarithmicMapping;
28
29impl FixedLogarithmicMapping {
30 const GAMMA: f64 = 1.02020202020202;
33
34 const MULTIPLIER: f64 = 49.99833328888678;
36
37 const RELATIVE_ACCURACY: f64 = 0.01;
39
40 #[inline]
44 pub const fn new() -> Self {
45 Self
46 }
47}
48
49impl IndexMapping for FixedLogarithmicMapping {
50 #[inline]
51 fn index(&self, value: f64) -> i32 {
52 let index = value.ln() * Self::MULTIPLIER;
53 if index >= 0.0 {
54 index as i32
55 } else {
56 (index as i32) - 1
57 }
58 }
59
60 #[inline]
61 fn value(&self, index: i32) -> f64 {
62 self.lower_bound(index) * (1.0 + self.relative_accuracy())
63 }
64
65 #[inline]
66 fn lower_bound(&self, index: i32) -> f64 {
67 (index as f64 / Self::MULTIPLIER).exp()
68 }
69
70 #[inline]
71 fn relative_accuracy(&self) -> f64 {
72 Self::RELATIVE_ACCURACY
73 }
74
75 #[inline]
76 fn min_indexable_value(&self) -> f64 {
77 f64::MIN_POSITIVE.max(Self::GAMMA.powf(i32::MIN as f64 + 1.0))
78 }
79
80 #[inline]
81 fn max_indexable_value(&self) -> f64 {
82 Self::GAMMA.powf(i32::MAX as f64 - 1.0).min(f64::MAX / Self::GAMMA)
83 }
84
85 #[inline]
86 fn gamma(&self) -> f64 {
87 Self::GAMMA
88 }
89
90 #[inline]
91 fn index_offset(&self) -> f64 {
92 0.0
93 }
94
95 #[inline]
96 fn interpolation(&self) -> Interpolation {
97 Interpolation::NONE
98 }
99
100 fn validate_proto_mapping(&self, proto: &ProtoIndexMapping) -> Result<(), ProtoConversionError> {
101 if !float_eq(proto.gamma, Self::GAMMA) {
103 return Err(ProtoConversionError::GammaMismatch {
104 expected: Self::GAMMA,
105 actual: proto.gamma,
106 });
107 }
108
109 if proto.indexOffset != 0.0 {
111 return Err(ProtoConversionError::NonZeroIndexOffset {
112 actual: proto.indexOffset,
113 });
114 }
115
116 let interpolation = proto.interpolation.enum_value_or_default();
118 if interpolation != Interpolation::NONE {
119 return Err(ProtoConversionError::UnsupportedInterpolation {
120 actual: interpolation as i32,
121 });
122 }
123
124 Ok(())
125 }
126
127 fn to_proto(&self) -> ProtoIndexMapping {
128 let mut proto = ProtoIndexMapping::new();
129 proto.gamma = Self::GAMMA;
130 proto.indexOffset = 0.0;
131 proto.interpolation = protobuf::EnumOrUnknown::new(Interpolation::NONE);
132 proto
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139 use crate::canonical::mapping::LogarithmicMapping;
140
141 #[test]
144 fn conforms_to_index_mapping_contract() {
145 let mapping = FixedLogarithmicMapping::new();
146 crate::canonical::mapping::conformance::assert_index_mapping_conformance(&mapping, 0.01);
147 }
148
149 #[test]
152 fn is_zero_sized() {
153 assert_eq!(std::mem::size_of::<FixedLogarithmicMapping>(), 0);
154 }
155
156 #[test]
157 fn matches_logarithmic_mapping() {
158 let fixed = FixedLogarithmicMapping::new();
159 let dynamic = LogarithmicMapping::new(0.01).unwrap();
160
161 assert!(
163 (fixed.gamma() - dynamic.gamma()).abs() < 1e-10,
164 "gamma mismatch: {} vs {}",
165 fixed.gamma(),
166 dynamic.gamma()
167 );
168 assert!(
169 (fixed.relative_accuracy() - dynamic.relative_accuracy()).abs() < 1e-10,
170 "relative_accuracy mismatch: {} vs {}",
171 fixed.relative_accuracy(),
172 dynamic.relative_accuracy()
173 );
174
175 for &value in &[0.001, 0.1, 1.0, 10.0, 100.0, 1000.0, 1_000_000.0] {
177 let fixed_idx = fixed.index(value);
178 let dynamic_idx = dynamic.index(value);
179 assert_eq!(
180 fixed_idx, dynamic_idx,
181 "index mismatch for value {}: {} vs {}",
182 value, fixed_idx, dynamic_idx
183 );
184 }
185
186 for idx in -100..100 {
188 let fixed_val = fixed.value(idx);
189 let dynamic_val = dynamic.value(idx);
190 assert!(
191 (fixed_val - dynamic_val).abs() < 1e-10 || (fixed_val / dynamic_val - 1.0).abs() < 1e-10,
192 "value mismatch for index {}: {} vs {}",
193 idx,
194 fixed_val,
195 dynamic_val
196 );
197 }
198 }
199}