bigdecimal/arithmetic/
inverse.rs

1//! inverse implementation
2
3use crate::*;
4
5/// Implementation of inverse: (1/n)
6pub(crate) fn impl_inverse_uint_scale(n: &BigUint, scale: i64, ctx: &Context) -> BigDecimal {
7    let guess = make_inv_guess(n.bits(), scale);
8    let max_precision = ctx.precision().get();
9
10    let s = BigDecimal::new(BigInt::from_biguint(Sign::Plus, n.clone()), scale);
11    let two = BigDecimal::from(2);
12
13    let next_iteration = move |r: BigDecimal| {
14        let tmp = &two - &s * &r;
15        r * tmp
16    };
17
18    // calculate first iteration
19    let mut running_result = next_iteration(guess);
20    debug_assert!(!running_result.is_zero(), "Zero detected in inverse calculation of {}e{}", n, -scale);
21
22    let mut prev_result = BigDecimal::one();
23    let mut result = BigDecimal::zero();
24
25    // TODO: Prove that we don't need to arbitrarily limit iterations
26    // and that convergence can be calculated
27    while prev_result != result {
28        // store current result to test for convergence
29        prev_result = result;
30
31        // calculate next iteration
32        running_result = next_iteration(running_result).with_prec(max_precision + 2);
33
34        // 'result' has clipped precision, 'running_result' has full precision
35        result = if running_result.digits() > max_precision {
36            running_result.with_precision_round(ctx.precision(), ctx.rounding_mode())
37        } else {
38            running_result.clone()
39        };
40    }
41
42    return result;
43}
44
45
46/// guess inverse based on the number of bits in the integer and decimal's scale
47fn make_inv_guess(bit_count: u64, scale: i64) -> BigDecimal {
48    // scale by ln(2)
49    let magic_factor = stdlib::f64::consts::LN_2;
50
51    let bit_count = bit_count as f64;
52    let initial_guess = magic_factor * exp2(-bit_count);
53    if initial_guess.is_finite() && initial_guess != 0.0 {
54        if let Ok(mut result) = BigDecimal::try_from(initial_guess) {
55            result.scale -= scale;
56            return result;
57        }
58    }
59
60    // backup guess for out-of-range integers
61
62    let approx_scale = bit_count * stdlib::f64::consts::LOG10_2;
63    let approx_scale_int = approx_scale.trunc();
64    let approx_scale_frac = approx_scale - approx_scale_int;
65
66    let recip = libm::exp10(-approx_scale_frac);
67    let mut res = BigDecimal::from_f32((magic_factor * recip) as f32).unwrap();
68    res.scale += approx_scale_int as i64;
69    res.scale -= scale;
70    return res;
71}
72
73
74#[cfg(test)]
75mod test_make_inv_guess {
76    use super::*;
77    use paste::paste;
78
79    macro_rules! impl_case {
80        ( $bin_count:literal, -$scale:literal => $expected:literal ) => {
81            paste! { impl_case!( [< case_ $bin_count _n $scale >]: $bin_count, -$scale => $expected); }
82        };
83        ( $bin_count:literal, $scale:literal => $expected:literal ) => {
84            paste! { impl_case!( [< case_ $bin_count _ $scale >]: $bin_count, $scale => $expected); }
85        };
86        ( $name:ident: $bin_count:expr, $scale:expr => $expected:literal ) => {
87            impl_case!($name: $bin_count, $scale, prec=5 => $expected);
88        };
89        ( $name:ident: $bin_count:expr, $scale:expr, prec=$prec:literal => $expected:literal ) => {
90            #[test]
91            fn $name() {
92                let guess = make_inv_guess($bin_count, $scale);
93                let expected: BigDecimal = $expected.parse().unwrap();
94                assert_eq!(guess.with_prec($prec), expected.with_prec($prec));
95            }
96        };
97    }
98
99    impl_case!(0, 0 => "0.69315");
100    impl_case!(1, 0 => "0.34657");
101    impl_case!(2, 0 => "0.17329");
102    impl_case!(2, 1 => "1.7329");
103
104    // 1 / (2^3 * 10^5) ~
105    impl_case!(3, -5 => "8.6643e-07");
106
107    // 2^-20
108    impl_case!(20, 0 => "6.6104e-07");
109    impl_case!(20, -900 => "6.6104E-907");
110    impl_case!(20, 800 => "6.6104E+793");
111
112    impl_case!(40, 10000 => "6.3041E+9987");
113
114    impl_case!(70, -5 => "5.8712e-27");
115    impl_case!(70, 5 => "5.8712e-17");
116    impl_case!(70, 50 => "5.8712e+28");
117
118    impl_case!(888, -300 => "3.3588E-568");
119    impl_case!(888, -19 => "3.3588E-287");
120    impl_case!(888, 0 => "3.3588E-268");
121    impl_case!(888, 270 => "335.88");
122
123    impl_case!(1022, 10 => "1.5423e-298");
124    impl_case!(1022, 308 => "1.5423");
125
126    impl_case!(1038, 316 => "2353.4");
127
128    impl_case!(case_31028_n659: 31028, -659 => "3.0347E-10000");
129    impl_case!(case_31028_0: 31028, 0 => "3.0347E-9341");
130    impl_case!(case_31028_1: 31028, 1 => "3.0347E-9340");
131    impl_case!(case_31028_9340: 31028, 9340 => ".30347");
132    impl_case!(case_31028_10000: 31028, 10000 => "3.0347E+659");
133
134    // impl_case!(case_max: u64::MAX, 270 => "335.88");
135}
136
137#[cfg(test)]
138mod test {
139    use super::*;
140    use stdlib::num::NonZeroU64;
141
142    #[test]
143    fn test_inverse_35543972957198043e291() {
144        let v = vec![
145            0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
146            0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
147            2324389888, 849200558
148        ];
149        let x = BigInt::new(Sign::Minus, v);
150        let d = BigDecimal::from(x);
151        let expected = "-2.813416500187520746852694701086705659180043761702417561798711758892800449936819185796527214192677476E-308".parse().unwrap();
152        assert_eq!(d.inverse(), expected);
153
154        assert_eq!(d.neg().inverse(), expected.neg());
155    }
156
157    macro_rules! impl_case {
158        ($name:ident: $prec:literal, $round:ident => $expected:literal) => {
159            #[test]
160            fn $name() {
161                let n = test_input();
162                let prec = NonZeroU64::new($prec).unwrap();
163                let rounding = RoundingMode::$round;
164                let ctx = Context::new(prec, rounding);
165
166                let result = n.inverse_with_context(&ctx);
167
168                let expected = $expected.parse().unwrap();
169                assert_eq!(&result, &expected);
170
171                let product = result * &n;
172                let epsilon = BigDecimal::new(BigInt::one(), $prec - 1);
173                let diff = (BigDecimal::one() - &product).abs();
174                assert!(diff < epsilon);
175            }
176        };
177    }
178
179    mod invert_seven {
180        use super::*;
181
182        fn test_input() -> BigDecimal {
183            BigDecimal::from(7u8)
184        }
185
186        impl_case!(case_prec10_round_down: 10, Down => "0.1428571428");
187        impl_case!(case_prec10_round_up: 10, Up => "0.1428571429");
188
189        impl_case!(case_prec11_round_ceiling: 11, Ceiling => "0.14285714286");
190    }
191
192
193    #[test]
194    fn inv_random_number() {
195       let n = BigDecimal::try_from(0.08121970592310568).unwrap();
196
197       let ctx = Context::new(NonZeroU64::new(40).unwrap(), RoundingMode::Down);
198       let i = n.inverse_with_context(&ctx);
199       assert_eq!(&i, &"12.31228294456944530942557443718279245563".parse().unwrap());
200
201       let product = i * &n;
202       assert!(BigDecimal::one() - &product < "1e-39".parse().unwrap());
203    }
204
205    #[cfg(property_tests)]
206    mod prop {
207        use super::*;
208        use proptest::*;
209        use num_traits::FromPrimitive;
210
211        proptest! {
212
213            #[test]
214            fn inverse_multiplies_to_one(f: f64, prec in 1..100u64) {
215                // ignore non-normal numbers
216                prop_assume!(f.is_normal());
217                prop_assume!(f != 0.0);
218
219                let n = BigDecimal::from_f64(f).unwrap();
220
221                let ctx = Context::new(NonZeroU64::new(prec).unwrap(), RoundingMode::Up);
222                let i = n.inverse_with_context(&ctx);
223                let product = &i * &n;
224
225                // accurate to precision minus one (due to rounding)
226                let epsilon = BigDecimal::new(1.into(), prec as i64 - 1);
227                let diff_from_one = BigDecimal::one() - &product;
228
229                prop_assert!(diff_from_one.abs() < epsilon, "{} >= {}", diff_from_one.abs(), epsilon);
230            }
231        }
232    }
233}