1use crate::FPDecimal;
2use primitive_types::U256;
3
4use cosmwasm_std::StdError;
5use std::cmp::Ordering;
6use std::{fmt::Display, str::FromStr};
7
8#[derive(Default)]
9pub enum RangeEnds {
10 #[default]
11 BothInclusive,
12 MinInclusive,
13 MaxInclusive,
14 Exclusive,
15}
16
17pub fn parse_dec(vs: &str, min: Option<&FPDecimal>, max: Option<&FPDecimal>, range_ends: RangeEnds) -> Result<FPDecimal, StdError> {
18 let v = FPDecimal::must_from_str(vs);
19 ensure_band(&v, min, max, range_ends)?;
20 Ok(v)
21}
22
23pub fn parse_int<T: FromStr + Ord + Display>(vs: &str, min: Option<&T>, max: Option<&T>, range_ends: RangeEnds) -> Result<T, StdError>
24where
25 <T as FromStr>::Err: ToString,
26{
27 match vs.parse::<T>() {
28 Ok(v) => {
29 ensure_band(&v, min, max, range_ends)?;
30 Ok(v)
31 }
32 Err(e) => Err(StdError::generic_err(e.to_string())),
33 }
34}
35
36pub fn ensure_band<T: Ord + Display>(v: &T, min: Option<&T>, max: Option<&T>, range_ends: RangeEnds) -> Result<(), StdError> {
37 if let Some(minv) = min {
38 match range_ends {
39 RangeEnds::BothInclusive | RangeEnds::MinInclusive => {
40 if v < minv {
41 return Err(StdError::generic_err(format!("value {v} must be >= {minv}")));
42 }
43 }
44 RangeEnds::MaxInclusive | RangeEnds::Exclusive => {
45 if v <= minv {
46 return Err(StdError::generic_err(format!("value {v} must be > {minv}")));
47 }
48 }
49 }
50 }
51 if let Some(maxv) = max {
52 match range_ends {
53 RangeEnds::BothInclusive | RangeEnds::MaxInclusive => {
54 if v > maxv {
55 return Err(StdError::generic_err(format!("value {v} must be <= {maxv}")));
56 }
57 }
58 RangeEnds::MinInclusive | RangeEnds::Exclusive => {
59 if v >= maxv {
60 return Err(StdError::generic_err(format!("value {v} must be < {maxv}")));
61 }
62 }
63 }
64 }
65 Ok(())
66}
67
68pub fn band_error_to_human(err: StdError, value_name: &str) -> StdError {
69 StdError::generic_err(format!("Value '{value_name}' failed validation due to: '{err}'"))
70}
71
72pub fn div_dec(num: FPDecimal, denom: FPDecimal) -> FPDecimal {
73 if denom == FPDecimal::ZERO {
74 denom
75 } else {
76 num / denom
77 }
78}
79
80pub fn floor(num: FPDecimal, min_tick: FPDecimal) -> FPDecimal {
81 assert!(min_tick >= FPDecimal::ZERO);
83 if num.is_zero() {
84 return num;
85 }
86 let remainder = num % min_tick;
87 num - remainder
88}
89
90pub fn round(num: FPDecimal, min_tick: FPDecimal) -> FPDecimal {
91 if min_tick < FPDecimal::must_from_str("0.00000001") {
92 panic!("min_tick should be greater than {}", FPDecimal::must_from_str("0.00000001"));
93 }
94 let num_floor = floor(num, min_tick);
95 let diff = num - num_floor;
96 match diff.cmp(&(min_tick / FPDecimal::TWO)) {
97 Ordering::Less => num_floor,
98 Ordering::Equal => {
99 if num_floor / (min_tick * FPDecimal::TWO) == FPDecimal::ZERO {
100 num_floor
101 } else {
102 num_floor + min_tick
103 }
104 }
105 Ordering::Greater => num_floor + min_tick,
106 }
107}
108
109pub fn round_to_min_tick(num: FPDecimal, min_tick: FPDecimal) -> FPDecimal {
110 if num < min_tick {
111 FPDecimal::ZERO
112 } else {
113 let shifted = div_dec(num, min_tick).int();
114 shifted * min_tick
115 }
116}
117
118pub fn round_to_nearest_tick(num: FPDecimal, min_tick: FPDecimal) -> FPDecimal {
119 if num < min_tick {
120 return FPDecimal::ZERO;
121 }
122
123 let remainder = FPDecimal::from(num.num % min_tick.num);
124 if remainder.num > min_tick.num / U256::from(2u64) {
125 FPDecimal::from(num.num - remainder.num + min_tick.num)
126 } else {
127 FPDecimal::from(num.num - remainder.num)
128 }
129}
130
131pub fn round_up_to_min_tick(num: FPDecimal, min_tick: FPDecimal) -> FPDecimal {
132 if num < min_tick {
133 return min_tick;
134 }
135
136 let remainder = FPDecimal::from(num.num % min_tick.num);
137
138 if remainder.num.is_zero() {
139 return num;
140 }
141
142 FPDecimal::from(num.num - remainder.num + min_tick.num)
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148 use crate::fp_decimal::scale::Scaled;
149
150 #[test]
151 fn test_floor() {
152 assert_eq!(floor(FPDecimal::must_from_str("0"), FPDecimal::must_from_str("0.1")), FPDecimal::ZERO);
153 assert_eq!(
154 floor(FPDecimal::must_from_str("0.13"), FPDecimal::must_from_str("0.1")),
155 FPDecimal::must_from_str("0.1")
156 );
157 assert_eq!(
158 floor(FPDecimal::must_from_str("0.19"), FPDecimal::must_from_str("0.1")),
159 FPDecimal::must_from_str("0.1")
160 );
161 assert_eq!(
162 floor(FPDecimal::must_from_str("1.19"), FPDecimal::must_from_str("0.1")),
163 FPDecimal::must_from_str("1.1")
164 );
165 assert_eq!(floor(FPDecimal::must_from_str("2.19"), FPDecimal::ONE), FPDecimal::TWO);
166
167 assert_eq!(floor(FPDecimal::must_from_str("-0"), FPDecimal::must_from_str("0.1")), FPDecimal::ZERO);
168 assert_eq!(
169 floor(FPDecimal::must_from_str("-0.13"), FPDecimal::must_from_str("0.1")),
170 FPDecimal::must_from_str("-0.2")
171 );
172 assert_eq!(
173 floor(FPDecimal::must_from_str("-0.19"), FPDecimal::must_from_str("0.1")),
174 FPDecimal::must_from_str("-0.2")
175 );
176 assert_eq!(
177 floor(FPDecimal::must_from_str("-1.19"), FPDecimal::must_from_str("0.1")),
178 FPDecimal::must_from_str("-1.2")
179 );
180 assert_eq!(
181 floor(FPDecimal::must_from_str("-2.19"), FPDecimal::must_from_str("0.1")),
182 FPDecimal::must_from_str("-2.2")
183 );
184
185 assert_eq!(floor(FPDecimal::must_from_str("-2.19"), FPDecimal::ONE), FPDecimal::must_from_str("-3"));
186 }
187
188 #[test]
189 fn test_round() {
190 assert_eq!(round(FPDecimal::must_from_str("0.13"), FPDecimal::ONE), FPDecimal::ZERO);
191 assert_eq!(round(FPDecimal::must_from_str("0.49"), FPDecimal::ONE), FPDecimal::ZERO);
192 assert_eq!(round(FPDecimal::must_from_str("0.5"), FPDecimal::ONE), FPDecimal::ZERO);
193 assert_eq!(round(FPDecimal::must_from_str("0.50009"), FPDecimal::ONE), FPDecimal::ONE);
194
195 assert_eq!(round(FPDecimal::must_from_str("-0.13"), FPDecimal::ONE), FPDecimal::ZERO);
196 assert_eq!(round(FPDecimal::must_from_str("-0.49"), FPDecimal::ONE), FPDecimal::ZERO);
197 assert_eq!(round(FPDecimal::must_from_str("-0.5"), FPDecimal::ONE), FPDecimal::ZERO);
198 assert_eq!(round(FPDecimal::must_from_str("-0.51"), FPDecimal::ONE), -FPDecimal::ONE);
199 assert_eq!(round(FPDecimal::must_from_str("-1.50009"), FPDecimal::ONE), -FPDecimal::TWO);
200 }
201
202 #[test]
203 fn test_round_with_scaled_numbers() {
204 assert_eq!(round(FPDecimal::must_from_str("0"), FPDecimal::must_from_str("0.1")), FPDecimal::ZERO);
205 assert_eq!(
206 round(FPDecimal::must_from_str("0.13"), FPDecimal::must_from_str("0.1")),
207 FPDecimal::must_from_str("0.1")
208 );
209 assert_eq!(
210 round(FPDecimal::must_from_str("0.50009"), FPDecimal::must_from_str("0.0001")),
211 FPDecimal::must_from_str("0.5001")
212 );
213
214 assert_eq!(round(FPDecimal::must_from_str("-0"), FPDecimal::must_from_str("0.1")), FPDecimal::ZERO);
215 assert_eq!(
216 round(FPDecimal::must_from_str("-0.13"), FPDecimal::must_from_str("0.1")),
217 FPDecimal::must_from_str("-0.1")
218 );
219 assert_eq!(
220 round(FPDecimal::must_from_str("-0.50009"), FPDecimal::must_from_str("0.0001")),
221 FPDecimal::must_from_str("-0.5001")
222 );
223
224 assert_eq!(round(FPDecimal::must_from_str("-1.50009"), FPDecimal::ONE.scaled(1)), FPDecimal::ZERO);
225 assert_eq!(
226 round(FPDecimal::must_from_str("-1.50009").scaled(1), FPDecimal::ONE.scaled(1)),
227 -FPDecimal::TWO.scaled(1)
228 );
229 assert_eq!(round(FPDecimal::must_from_str("-1.50009"), FPDecimal::ONE.scaled(1)), FPDecimal::ZERO);
230 assert_eq!(
231 round(FPDecimal::must_from_str("-1.50009").scaled(1), FPDecimal::ONE.scaled(1)),
232 -FPDecimal::TWO.scaled(1)
233 );
234 }
235
236 #[test]
237 fn test_div_dec() {
238 assert_eq!(
239 div_dec(FPDecimal::must_from_str("6"), FPDecimal::must_from_str("2")),
240 FPDecimal::must_from_str("3")
241 );
242 assert_eq!(
243 div_dec(FPDecimal::must_from_str("7"), FPDecimal::must_from_str("0")),
244 FPDecimal::must_from_str("0")
245 );
246 assert_eq!(
247 div_dec(FPDecimal::must_from_str("7.5"), FPDecimal::must_from_str("2.5")),
248 FPDecimal::must_from_str("3.0")
249 );
250 }
251
252 #[test]
253 fn test_round_to_min_tick() {
254 assert_eq!(
255 round_to_min_tick(FPDecimal::must_from_str("7.7"), FPDecimal::must_from_str("2.0")),
256 FPDecimal::must_from_str("6.0")
257 );
258 assert_eq!(
259 round_to_min_tick(FPDecimal::must_from_str("1.5"), FPDecimal::must_from_str("2.0")),
260 FPDecimal::must_from_str("0.0")
261 );
262 assert_eq!(
263 round_to_min_tick(FPDecimal::must_from_str("10.0"), FPDecimal::must_from_str("3.0")),
264 FPDecimal::must_from_str("9.0")
265 );
266 }
267
268 #[test]
269 fn round_to_nearest_tick_test() {
270 assert_eq!(
271 round_to_nearest_tick(FPDecimal::must_from_str("7.7"), FPDecimal::must_from_str("2.0")),
272 FPDecimal::must_from_str("8.0")
273 );
274 assert_eq!(
275 round_to_nearest_tick(FPDecimal::must_from_str("1.5"), FPDecimal::must_from_str("2.0")),
276 FPDecimal::must_from_str("0.0")
277 );
278 assert_eq!(
279 round_to_nearest_tick(FPDecimal::must_from_str("2.5"), FPDecimal::must_from_str("2.0")),
280 FPDecimal::must_from_str("2.0")
281 );
282 assert_eq!(
283 round_to_nearest_tick(
284 FPDecimal::must_from_str("0.000000057575228461"),
285 FPDecimal::must_from_str("0.00000000000001")
286 ),
287 FPDecimal::must_from_str("0.00000005757523")
288 );
289
290 assert_eq!(
291 round_to_nearest_tick(FPDecimal::must_from_str("10.0"), FPDecimal::must_from_str("3.0")),
292 FPDecimal::must_from_str("9.0")
293 );
294 let data = vec![
296 ["1.09932", "1.1"],
297 ["2.032", "2.03"],
298 ["1.0009932", "1"],
299 ["1.009932", "1.01"],
300 ["0.9932", "0.99"],
301 ];
302 let precision = FPDecimal::must_from_str("0.01");
303
304 for item in &data {
305 let input = FPDecimal::must_from_str(item[0]);
306 let expected = FPDecimal::must_from_str(item[1]);
307
308 let output = round_to_nearest_tick(input, precision);
309 assert_eq!(expected, output);
310 }
311 }
312
313 #[test]
314 fn test_round_up_to_min_tick() {
315 let num = FPDecimal::from(37u128);
316 let min_tick = FPDecimal::from(10u128);
317
318 let result = round_up_to_min_tick(num, min_tick);
319 assert_eq!(result, FPDecimal::from(40u128));
320
321 let num = FPDecimal::must_from_str("0.00000153");
322 let min_tick = FPDecimal::must_from_str("0.000001");
323
324 let result = round_up_to_min_tick(num, min_tick);
325 assert_eq!(result, FPDecimal::must_from_str("0.000002"));
326
327 let num = FPDecimal::must_from_str("0.000001");
328 let min_tick = FPDecimal::must_from_str("0.000001");
329
330 let result = round_up_to_min_tick(num, min_tick);
331 assert_eq!(result, FPDecimal::must_from_str("0.000001"));
332
333 let num = FPDecimal::must_from_str("0.0000001");
334 let min_tick = FPDecimal::must_from_str("0.000001");
335
336 let result = round_up_to_min_tick(num, min_tick);
337 assert_eq!(result, FPDecimal::must_from_str("0.000001"));
338 }
339}