psy_math/
number.rs

1//! Yet another decimal library.
2
3use std::{
4    iter::Sum,
5    ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign},
6};
7
8use bytemuck::{Pod, Zeroable};
9use std::fmt::{Display, Formatter};
10use thiserror::Error;
11use uint::construct_uint;
12
13construct_uint! {
14    #[derive(Pod, Zeroable)]
15    pub struct U192(3);
16}
17
18pub const BPS_EXPONENT: i32 = -4;
19const PRECISION: i32 = 15;
20const ONE: U192 = U192([1_000_000_000_000_000, 0, 0]);
21const U64_MAX: U192 = U192([0xffffffffffffffff, 0x0, 0x0]);
22
23/// A large unsigned integer
24#[derive(Pod, Zeroable, Default, Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
25#[repr(transparent)]
26pub struct Number(U192);
27
28static_assertions::const_assert_eq!(24, std::mem::size_of::<Number>());
29static_assertions::const_assert_eq!(0, std::mem::size_of::<Number>() % 8);
30impl Number {
31    pub const ONE: Number = Number(ONE);
32    pub const ZERO: Number = Number(U192::zero());
33
34    /// Convert this number to fit in a u64
35    ///
36    /// The precision of the number in the u64 is based on the
37    /// exponent provided.
38    pub fn as_u64(&self, exponent: impl Into<i32>) -> u64 {
39        let extra_precision = PRECISION + exponent.into();
40        let prec_value = Self::ten_pow(extra_precision.abs() as u32);
41
42        let target_value = if extra_precision < 0 {
43            self.0 * prec_value
44        } else {
45            self.0 / prec_value
46        };
47
48        if target_value > U64_MAX {
49            panic!("cannot convert to u64 due to overflow");
50        }
51
52        target_value.as_u64()
53    }
54
55    /// Ceiling value of number, fit in a u64
56    ///
57    /// The precision of the number in the u64 is based on the
58    /// exponent provided.
59    ///
60    /// The result is rounded up to the nearest one, based on the
61    /// target precision.
62    pub fn as_u64_ceil(&self, exponent: impl Into<i32>) -> u64 {
63        let extra_precision = PRECISION + exponent.into();
64        let prec_value = Self::ten_pow(extra_precision.abs() as u32);
65
66        let target_rounded = prec_value - U192::from(1) + self.0;
67        let target_value = if extra_precision < 0 {
68            target_rounded * prec_value
69        } else {
70            target_rounded / prec_value
71        };
72
73        if target_value > U64_MAX {
74            panic!("cannot convert to u64 due to overflow");
75        }
76
77        target_value.as_u64()
78    }
79
80    /// Convert this number to fit in a u64
81    ///
82    /// The precision of the number in the u64 is based on the
83    /// exponent provided.
84    ///
85    /// The result is rounded to the nearest one, based on the
86    /// target precision.
87    pub fn as_u64_rounded(&self, exponent: impl Into<i32>) -> u64 {
88        let extra_precision = PRECISION + exponent.into();
89        let prec_value = Self::ten_pow(extra_precision.abs() as u32);
90
91        let rounding = match extra_precision > 0 {
92            true => U192::from(1) * prec_value / 2,
93            false => U192::zero(),
94        };
95
96        let target_rounded = rounding + self.0;
97        let target_value = if extra_precision < 0 {
98            target_rounded * prec_value
99        } else {
100            target_rounded / prec_value
101        };
102
103        if target_value > U64_MAX {
104            panic!("cannot convert to u64 due to overflow");
105        }
106
107        target_value.as_u64()
108    }
109
110    /// Convert another integer into a `Number`.
111    pub fn from_decimal(value: impl Into<U192>, exponent: impl Into<i32>) -> Self {
112        let extra_precision = PRECISION + exponent.into();
113        let prec_value = Self::ten_pow(extra_precision.abs() as u32);
114
115        if extra_precision < 0 {
116            Self(value.into() / prec_value)
117        } else {
118            Self(value.into() * prec_value)
119        }
120    }
121
122    /// Convert from basis points into a `Number`
123    pub fn from_bps(basis_points: u16) -> Number {
124        Number::from_decimal(basis_points, BPS_EXPONENT)
125    }
126
127    pub fn pow(&self, exp: impl Into<Number>) -> Number {
128        let value = self.0.pow(exp.into().0);
129
130        Self(value)
131    }
132
133    pub fn saturating_add(&self, n: Number) -> Number {
134        Number(self.0.saturating_add(n.0))
135    }
136
137    pub fn saturating_sub(&self, n: Number) -> Number {
138        Number(self.0.saturating_sub(n.0))
139    }
140
141    pub fn saturating_mul(&self, n: Number) -> Number {
142        Number(self.0.saturating_mul(n.0))
143    }
144
145    pub fn ten_pow(exponent: u32) -> U192 {
146        let value: u64 = match exponent {
147            16 => 10_000_000_000_000_000,
148            15 => 1_000_000_000_000_000,
149            14 => 100_000_000_000_000,
150            13 => 10_000_000_000_000,
151            12 => 1_000_000_000_000,
152            11 => 100_000_000_000,
153            10 => 10_000_000_000,
154            9 => 1_000_000_000,
155            8 => 100_000_000,
156            7 => 10_000_000,
157            6 => 1_000_000,
158            5 => 100_000,
159            4 => 10_000,
160            3 => 1_000,
161            2 => 100,
162            1 => 10,
163            0 => 1,
164            _ => panic!("no support for exponent: {}", exponent),
165        };
166
167        value.into()
168    }
169
170    /// Get the underlying representation in bits
171    pub fn into_bits(self) -> [u8; 24] {
172        unsafe { std::mem::transmute(self.0 .0) }
173    }
174
175    /// Read a number from a raw 196-bit representation, which was previously
176    /// returned by a call to `into_bits`.
177    pub fn from_bits(bits: [u8; 24]) -> Self {
178        Self(U192(unsafe { std::mem::transmute(bits) }))
179    }
180}
181
182impl<T: Into<U192>> From<T> for Number {
183    fn from(n: T) -> Number {
184        Number(n.into() * ONE)
185    }
186}
187
188impl From<Number> for [u8; 24] {
189    fn from(n: Number) -> Self {
190        n.0.into()
191    }
192}
193
194impl Display for Number {
195    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
196        // todo optimize
197        let rem = self.0 % ONE;
198        let decimal_digits = PRECISION as usize;
199        let rem_str = rem.to_string();
200        // regular padding like {:010} doesn't work with U192
201        let decimals = "0".repeat(decimal_digits - rem_str.len()) + &*rem_str;
202        let stripped_decimals = decimals.trim_end_matches('0');
203        let pretty_decimals = if stripped_decimals.is_empty() {
204            "0"
205        } else {
206            stripped_decimals
207        };
208        if self.0 < ONE {
209            write!(f, "0.{}", pretty_decimals)?;
210        } else {
211            let int = self.0 / ONE;
212            write!(f, "{}.{}", int, pretty_decimals)?;
213        }
214        Ok(())
215    }
216}
217
218#[derive(Error, Debug, Clone, Eq, PartialEq)]
219pub enum Error {
220    #[error("An integer value overflowed")]
221    Overflow(Number),
222
223    #[error("Attempting to divide by zero")]
224    DivideByZero,
225}
226
227impl Add<Number> for Number {
228    type Output = Number;
229
230    fn add(self, rhs: Number) -> Self::Output {
231        Self(self.0.add(rhs.0))
232    }
233}
234
235impl AddAssign<Number> for Number {
236    fn add_assign(&mut self, rhs: Number) {
237        self.0.add_assign(rhs.0)
238    }
239}
240
241impl SubAssign<Number> for Number {
242    fn sub_assign(&mut self, rhs: Number) {
243        self.0.sub_assign(rhs.0)
244    }
245}
246
247impl Sub<Number> for Number {
248    type Output = Number;
249
250    fn sub(self, rhs: Number) -> Self::Output {
251        Self(self.0.sub(rhs.0))
252    }
253}
254
255impl Mul<Number> for Number {
256    type Output = Number;
257
258    fn mul(self, rhs: Number) -> Self::Output {
259        Self(self.0.mul(rhs.0).div(ONE))
260    }
261}
262
263impl MulAssign<Number> for Number {
264    fn mul_assign(&mut self, rhs: Number) {
265        self.0.mul_assign(rhs.0);
266        self.0.div_assign(ONE);
267    }
268}
269
270impl Div<Number> for Number {
271    type Output = Number;
272
273    fn div(self, rhs: Number) -> Self::Output {
274        Self(self.0.mul(ONE).div(rhs.0))
275    }
276}
277
278impl<T: Into<U192>> Mul<T> for Number {
279    type Output = Number;
280
281    fn mul(self, rhs: T) -> Self::Output {
282        Self(self.0.mul(rhs.into()))
283    }
284}
285
286impl<T: Into<U192>> Div<T> for Number {
287    type Output = Number;
288
289    fn div(self, rhs: T) -> Self::Output {
290        Self(self.0.div(rhs.into()))
291    }
292}
293
294impl Sum for Number {
295    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
296        iter.reduce(|a, b| a + b).unwrap_or(Self::ZERO)
297    }
298}
299
300/// Computes the Taylor expansion of exp(x) - 1, using the
301/// indicated number of terms.
302/// For example,
303///     expm1_approx(x, 3) = x + x^2 / 2 + x^3 / 6
304pub fn expm1_approx(x: Number, terms: usize) -> Number {
305    if terms == 0 {
306        return 0.into();
307    }
308    if terms == 1 {
309        return x;
310    }
311
312    let mut z = x;
313    let mut acc = x;
314    let mut fac = 1u64;
315
316    for k in 2..terms + 1 {
317        z *= x;
318        fac *= k as u64;
319        acc += z / fac;
320    }
321
322    acc
323}
324
325#[cfg(test)]
326mod tests {
327    use super::*;
328
329    // For reference:
330    // const SECONDS_PER_HOUR: u64 = 3600;
331    // const SECONDS_PER_2H: u64 = SECONDS_PER_HOUR * 2;
332    // const SECONDS_PER_12H: u64 = SECONDS_PER_HOUR * 12;
333    // const SECONDS_PER_DAY: u64 = SECONDS_PER_HOUR * 24;
334    // const SECONDS_PER_WEEK: u64 = SECONDS_PER_DAY * 7;
335    // //Note: 365 days, does not account for leap-days
336    // const SECONDS_PER_YEAR: u64 = 31_536_000;
337    // const MAX_ACCRUAL_SECONDS: u64 = SECONDS_PER_WEEK;
338
339    #[test]
340    fn test_taylor_approx_point2ish() {
341        /*
342           x = .2
343           e^.2 ~= 1.221402758160170
344           e^.2 - 1 ~= 0.221402758160170
345        */
346        let expected: u64 = 221_402_758_160_170;
347        let expected_number: Number = Number::from_decimal(expected, -15);
348        // 221_402_666_666_665 <- actual result
349        let answer = expm1_approx(Number::from_decimal(2, -1), 5);
350        let tolerance = Number::from_decimal(10_000_000_000 as u64, -15);
351
352        let diff = if expected_number.gt(&answer) {
353            expected_number.sub(answer)
354        } else {
355            answer.sub(expected_number)
356        };
357        assert!(diff.lt(&tolerance));
358    }
359
360    #[test]
361    fn test_taylor_approx_point3ish() {
362        /*
363           x = .3
364           e^.3 ~= 1.349858807576000
365           e^.3 - 1 ~= 0.349858807576000
366        */
367        let expected: u64 = 349_858_807_576_000;
368        let expected_number: Number = Number::from_decimal(expected, -15);
369        // 349_857_750_000_000 <- actual result
370        let answer = expm1_approx(Number::from_decimal(3, -1), 5);
371        let tolerance = Number::from_decimal(10_000_000_000 as u64, -15);
372
373        let diff = if expected_number.gt(&answer) {
374            expected_number.sub(answer)
375        } else {
376            answer.sub(expected_number)
377        };
378
379        assert!(diff.lt(&tolerance));
380    }
381
382    #[test]
383    fn test_taylor_approx_maxish() {
384        // assuming a max rate of 400%
385        // max_rate * seconds_per_week / seconds_per_year = 4 * 604800 / 31536000
386        //    = 0.076712328767123 = 76712328767123 * 10^-15
387        let max_x = Number::from_decimal(76712328767123 as u64, -15);
388
389        /*
390           x = .076712328767123
391           e^x ~= 1.079731424041940
392           e^x - 1 ~= 0.079731424041940
393        */
394        let expected: u64 = 079_731_424_041_940;
395        let expected_number: Number = Number::from_decimal(expected, -15);
396        // 079_731_423_755_760 <- actual result
397        let answer = expm1_approx(max_x, 5);
398        let tolerance = Number::from_decimal(10_000_000_000 as u64, -15);
399
400        let diff = if expected_number.gt(&answer) {
401            expected_number.sub(answer)
402        } else {
403            answer.sub(expected_number)
404        };
405
406        assert!(diff.lt(&tolerance));
407    }
408
409    #[test]
410    fn test_taylor_approx_minish() {
411        let min_x = Number::from_decimal(50 as u64, -15);
412
413        /*
414           x = 0.00000000000005
415           e^x ~= 1.000000000000050
416           e^x - 1 ~= 0.000000000000050
417        */
418        let expected: u64 = 000_000_000_000_050;
419        let expected_number: Number = Number::from_decimal(expected, -15);
420        // 000_000_000_000_050 <- actual result
421        let answer = expm1_approx(min_x, 5);
422        let tolerance = Number::from_decimal(100 as u64, -15);
423
424        let diff = if expected_number.gt(&answer) {
425            expected_number.sub(answer)
426        } else {
427            answer.sub(expected_number)
428        };
429
430        assert!(diff.lt(&tolerance));
431    }
432
433    #[test]
434    fn zero_equals_zero() {
435        assert_eq!(Number::ZERO, Number::from_decimal(0, 0));
436        assert_eq!(Number::ZERO, Number::from(0u64));
437    }
438
439    #[test]
440    fn one_equals_one() {
441        assert_eq!(Number::ONE, Number::from_decimal(1, 0));
442        assert_eq!(Number::ONE, Number::from(1u64));
443    }
444
445    #[test]
446    fn one_plus_one_equals_two() {
447        assert_eq!(Number::from_decimal(2, 0), Number::ONE + Number::ONE);
448    }
449
450    #[test]
451    fn one_minus_one_equals_zero() {
452        assert_eq!(Number::ONE - Number::ONE, Number::ZERO);
453    }
454
455    #[test]
456    fn one_times_one_equals_one() {
457        assert_eq!(Number::ONE, Number::ONE * Number::ONE);
458    }
459
460    #[test]
461    fn one_divided_by_one_equals_one() {
462        assert_eq!(Number::ONE, Number::ONE / Number::ONE);
463    }
464
465    #[test]
466    fn ten_div_100_equals_point_1() {
467        assert_eq!(
468            Number::from_decimal(1, -1),
469            Number::from_decimal(1, 1) / Number::from_decimal(100, 0)
470        );
471    }
472
473    #[test]
474    fn multiply_by_u64() {
475        assert_eq!(
476            Number::from_decimal(3, 1),
477            Number::from_decimal(1, 1) * 3u64
478        )
479    }
480
481    #[test]
482    fn ceil_gt_one() {
483        assert_eq!(Number::from_decimal(11, -1).as_u64_ceil(0), 2u64);
484        assert_eq!(Number::from_decimal(19, -1).as_u64_ceil(0), 2u64);
485    }
486
487    #[test]
488    fn ceil_lt_one() {
489        assert_eq!(Number::from_decimal(1, -1).as_u64_ceil(0), 1u64);
490        assert_eq!(Number::from_decimal(1, -10).as_u64_ceil(0), 1u64);
491    }
492
493    #[test]
494    fn ceil_of_int() {
495        assert_eq!(Number::from_decimal(1, 0).as_u64_ceil(0), 1u64);
496        assert_eq!(
497            Number::from_decimal(1_000_000u64, 0).as_u64_ceil(0),
498            1_000_000u64
499        );
500    }
501
502    #[test]
503    fn to_string() {
504        assert_eq!("1000.0", Number::from(1000).to_string());
505        assert_eq!("1.0", Number::from(1).to_string());
506        assert_eq!("0.001", Number::from_decimal(1, -3).to_string());
507    }
508
509    #[test]
510    fn into_bits() {
511        let bits = Number::from_decimal(1242, -3).into_bits();
512        let number = Number::from_bits(bits);
513
514        assert_eq!(Number::from_decimal(1242, -3), number);
515    }
516}