soroban_fixed_point_math/
u128.rs

1use soroban_sdk::{unwrap::UnwrapOptimized, Env, U256};
2
3use crate::{fixed_point::FixedPoint, SorobanFixedPoint};
4
5impl FixedPoint for u128 {
6    fn fixed_mul_floor(self, y: u128, denominator: u128) -> Option<u128> {
7        mul_div_floor(self, y, denominator)
8    }
9
10    fn fixed_mul_ceil(self, y: u128, denominator: u128) -> Option<u128> {
11        mul_div_ceil(self, y, denominator)
12    }
13
14    fn fixed_div_floor(self, y: u128, denominator: u128) -> Option<u128> {
15        mul_div_floor(self, denominator, y)
16    }
17
18    fn fixed_div_ceil(self, y: u128, denominator: u128) -> Option<u128> {
19        mul_div_ceil(self, denominator, y)
20    }
21}
22
23/// Performs floor(x * y / z)
24pub(crate) fn mul_div_floor(x: u128, y: u128, z: u128) -> Option<u128> {
25    let r = x.checked_mul(y)?;
26    r.checked_div(z)
27}
28
29/// Performs ceil(x * y / z)
30pub(crate) fn mul_div_ceil(x: u128, y: u128, z: u128) -> Option<u128> {
31    let r = x.checked_mul(y)?;
32    div_ceil(r, z)
33}
34
35/// Performs ceil(r / z)
36fn div_ceil(r: u128, z: u128) -> Option<u128> {
37    // floor taken by default for a positive result
38    let remainder = r.checked_rem_euclid(z)?;
39    (r / z).checked_add(if remainder > 0 { 1 } else { 0 })
40}
41
42impl SorobanFixedPoint for u128 {
43    fn fixed_mul_floor(&self, env: &Env, y: &u128, denominator: &u128) -> u128 {
44        scaled_mul_div_floor(self, env, y, denominator)
45    }
46
47    fn fixed_mul_ceil(&self, env: &Env, y: &u128, denominator: &u128) -> u128 {
48        scaled_mul_div_ceil(&self, env, y, denominator)
49    }
50
51    fn fixed_div_floor(&self, env: &Env, y: &u128, denominator: &u128) -> u128 {
52        scaled_mul_div_floor(self, env, denominator, y)
53    }
54
55    fn fixed_div_ceil(&self, env: &Env, y: &u128, denominator: &u128) -> u128 {
56        scaled_mul_div_ceil(self, env, denominator, y)
57    }
58}
59
60/// Performs floor(x * y / z)
61fn scaled_mul_div_floor(x: &u128, env: &Env, y: &u128, z: &u128) -> u128 {
62    return match x.checked_mul(*y) {
63        Some(r) => r.checked_div(*z).unwrap_optimized(),
64        None => {
65            // scale to U256 and retry
66            let res = crate::u256::mul_div_floor(
67                &U256::from_u128(&env, *x),
68                &U256::from_u128(&env, *y),
69                &U256::from_u128(&env, *z),
70            );
71            // will panic if result is not representable in u128
72            res.to_u128().unwrap_optimized()
73        }
74    };
75}
76
77/// Performs floor(x * y / z)
78fn scaled_mul_div_ceil(x: &u128, env: &Env, y: &u128, z: &u128) -> u128 {
79    return match x.checked_mul(*y) {
80        Some(r) => div_ceil(r, *z).unwrap_optimized(),
81        None => {
82            // scale to U256 and retry
83            let res = crate::u256::mul_div_ceil(
84                &env,
85                &U256::from_u128(&env, *x),
86                &U256::from_u128(&env, *y),
87                &U256::from_u128(&env, *z),
88            );
89            // will panic if result is not representable in u128
90            res.to_u128().unwrap_optimized()
91        }
92    };
93}
94
95#[cfg(test)]
96mod test_fixed_point {
97
98    /********** fixed_mul_floor **********/
99
100    use crate::FixedPoint;
101
102    #[test]
103    fn test_fixed_mul_floor_rounds_down() {
104        let x: u128 = 1_5391283;
105        let y: u128 = 314_1592653;
106        let denominator: u128 = 1_0000001;
107
108        let result = x.fixed_mul_floor(y, denominator).unwrap();
109
110        assert_eq!(result, 483_5313675)
111    }
112
113    #[test]
114    fn test_fixed_mul_floor_large_number() {
115        let x: u128 = 340_282_366_920_938_463_463;
116        let y: u128 = 1_000_000_000_000_000_000;
117        let denominator: u128 = 1_000_000_000_000_000_000;
118
119        let result = x.fixed_mul_floor(y, denominator).unwrap();
120
121        assert_eq!(result, 340_282_366_920_938_463_463)
122    }
123
124    #[test]
125    fn test_fixed_mul_floor_phantom_overflow() {
126        let x: u128 = 340_282_366_920_938_463_463;
127        let y: u128 = 1_000_000_000_000_000_001;
128        let denominator: u128 = 1_000_000_000_000_000_000;
129
130        let result = x.fixed_mul_floor(y, denominator);
131
132        assert_eq!(None, result);
133    }
134
135    /********** fixed_mul_ceil **********/
136
137    #[test]
138    fn test_fixed_mul_ceil_rounds_up() {
139        let x: u128 = 1_5391283;
140        let y: u128 = 314_1592653;
141        let denominator: u128 = 1_0000001;
142
143        let result = x.fixed_mul_ceil(y, denominator).unwrap();
144
145        assert_eq!(result, 483_5313676)
146    }
147
148    #[test]
149    fn test_fixed_mul_ceil_large_number() {
150        let x: u128 = 340_282_366_920_938_463_463;
151        let y: u128 = 1_000_000_000_000_000_000;
152        let denominator: u128 = 1_000_000_000_000_000_000;
153
154        let result = x.fixed_mul_ceil(y, denominator).unwrap();
155
156        assert_eq!(result, 340_282_366_920_938_463_463)
157    }
158
159    #[test]
160    fn test_fixed_mul_ceil_phantom_overflow() {
161        let x: u128 = 340_282_366_920_938_463_463;
162        let y: u128 = 1_000_000_000_000_000_001;
163        let denominator: u128 = 1_000_000_000_000_000_000;
164
165        let result = x.fixed_mul_ceil(y, denominator);
166
167        assert_eq!(None, result);
168    }
169
170    /********** fixed_div_floor **********/
171
172    #[test]
173    fn test_fixed_div_floor_rounds_down() {
174        let x: u128 = 314_1592653;
175        let y: u128 = 1_5391280;
176        let denominator: u128 = 1_0000000;
177
178        let result = x.fixed_div_floor(y, denominator).unwrap();
179
180        assert_eq!(result, 204_1150997)
181    }
182
183    #[test]
184    fn test_fixed_div_floor_large_number() {
185        let x: u128 = 340_282_366_920_938_463_463;
186        let y: u128 = 1_000_000_000_000_000_000;
187        let denominator: u128 = 1_000_000_000_000_000_000;
188
189        let result = x.fixed_div_floor(y, denominator).unwrap();
190
191        assert_eq!(result, 340_282_366_920_938_463_463)
192    }
193
194    #[test]
195    fn test_fixed_div_floor_phantom_overflow() {
196        let x: u128 = 340_282_366_920_938_463_463;
197        let y: u128 = 1_000_000_000_000_000_000;
198        let denominator: u128 = 1_000_000_000_000_000_001;
199
200        let result = x.fixed_div_floor(y, denominator);
201
202        assert_eq!(None, result);
203    }
204
205    /********** fixed_div_ceil **********/
206
207    #[test]
208    fn test_fixed_div_ceil_rounds_down() {
209        let x: u128 = 314_1592653;
210        let y: u128 = 1_5391280;
211        let denominator: u128 = 1_0000000;
212
213        let result = x.fixed_div_ceil(y, denominator).unwrap();
214
215        assert_eq!(result, 204_1150998)
216    }
217
218    #[test]
219    fn test_fixed_div_ceil_large_number() {
220        let x: u128 = 340_282_366_920_938_463_463;
221        let y: u128 = 1_000_000_000_000_000_000;
222        let denominator: u128 = 1_000_000_000_000_000_000;
223
224        let result = x.fixed_div_ceil(y, denominator).unwrap();
225
226        assert_eq!(result, 340_282_366_920_938_463_463)
227    }
228
229    #[test]
230    fn test_fixed_div_ceil_phantom_overflow() {
231        let x: u128 = 340_282_366_920_938_463_463;
232        let y: u128 = 1_000_000_000_000_000_000;
233        let denominator: u128 = 1_000_000_000_000_000_001;
234
235        let result = x.fixed_div_ceil(y, denominator);
236
237        assert_eq!(None, result);
238    }
239}
240
241#[cfg(test)]
242mod test_soroban_fixed_point {
243    use crate::SorobanFixedPoint;
244    use soroban_sdk::Env;
245
246    /********** fixed_mul_floor **********/
247
248    #[test]
249    fn test_fixed_mul_floor_rounds_down() {
250        let env = Env::default();
251        let x: u128 = 1_5391283;
252        let y: u128 = 314_1592653;
253        let denominator: u128 = 1_0000001;
254
255        let result = x.fixed_mul_floor(&env, &y, &denominator);
256
257        assert_eq!(result, 483_5313675)
258    }
259
260    #[test]
261    fn test_fixed_mul_floor_phantom_overflow_scales() {
262        let env = Env::default();
263        let x: u128 = 340_282_366_920_938_463_463;
264        let y: u128 = 10u128.pow(27);
265        let denominator: u128 = 10u128.pow(18);
266
267        let result = x.fixed_mul_floor(&env, &y, &denominator);
268
269        assert_eq!(result, 340_282_366_920_938_463_463 * 10u128.pow(9));
270    }
271
272    /********** fixed_mul_ceil **********/
273
274    #[test]
275    fn test_fixed_mul_ceil_rounds_up() {
276        let env = Env::default();
277        let x: u128 = 1_5391283;
278        let y: u128 = 314_1592653;
279        let denominator: u128 = 1_0000001;
280
281        let result = x.fixed_mul_ceil(&env, &y, &denominator);
282
283        assert_eq!(result, 483_5313676)
284    }
285
286    #[test]
287    fn test_fixed_mul_ceil_large_number() {
288        let env = Env::default();
289        let x: u128 = 340_282_366_920_938_463_463;
290        let y: u128 = 1_000_000_000_000_000_000;
291        let denominator: u128 = 1_000_000_000_000_000_000;
292
293        let result = x.fixed_mul_ceil(&env, &y, &denominator);
294
295        assert_eq!(result, 340_282_366_920_938_463_463)
296    }
297
298    #[test]
299    fn test_fixed_mul_ceil_phantom_overflow_scales() {
300        let env = Env::default();
301        let x: u128 = 340_282_366_920_938_463_463;
302        let y: u128 = 10u128.pow(27);
303        let denominator: u128 = 10u128.pow(18);
304
305        let result = x.fixed_mul_ceil(&env, &y, &denominator);
306
307        assert_eq!(result, 340_282_366_920_938_463_463 * 10u128.pow(9));
308    }
309
310    /********** fixed_div_floor **********/
311
312    #[test]
313    fn test_fixed_div_floor_rounds_down() {
314        let env = Env::default();
315        let x: u128 = 314_1592653;
316        let y: u128 = 1_5391280;
317        let denominator: u128 = 1_0000000;
318
319        let result = x.fixed_div_floor(&env, &y, &denominator);
320
321        assert_eq!(result, 204_1150997)
322    }
323
324    #[test]
325    fn test_fixed_div_floor_phantom_overflow_scales() {
326        let env = Env::default();
327        let x: u128 = 340_282_366_920_938_463_463;
328        let y: u128 = 10u128.pow(18);
329        let denominator: u128 = 10u128.pow(27);
330
331        let result = x.fixed_div_floor(&env, &y, &denominator);
332
333        assert_eq!(result, 340_282_366_920_938_463_463 * 10u128.pow(9));
334    }
335
336    /********** fixed_div_ceil **********/
337
338    #[test]
339    fn test_fixed_div_ceil_rounds_down() {
340        let env = Env::default();
341        let x: u128 = 314_1592653;
342        let y: u128 = 1_5391280;
343        let denominator: u128 = 1_0000000;
344
345        let result = x.fixed_div_ceil(&env, &y, &denominator);
346
347        assert_eq!(result, 204_1150998)
348    }
349
350    #[test]
351    fn test_fixed_div_ceil_large_number() {
352        let env = Env::default();
353        let x: u128 = 340_282_366_920_938_463_463;
354        let y: u128 = 1_000_000_000_000_000_000;
355        let denominator: u128 = 1_000_000_000_000_000_000;
356
357        let result = x.fixed_div_ceil(&env, &y, &denominator);
358
359        assert_eq!(result, 340_282_366_920_938_463_463)
360    }
361
362    #[test]
363    fn test_fixed_div_ceil_phantom_overflow_scales() {
364        let env = Env::default();
365        let x: u128 = 340_282_366_920_938_463_463;
366        let y: u128 = 10u128.pow(18);
367        let denominator: u128 = 10u128.pow(27);
368
369        let result = x.fixed_div_floor(&env, &y, &denominator);
370
371        assert_eq!(result, 340_282_366_920_938_463_463 * 10u128.pow(9));
372    }
373}