bigdecimal/arithmetic/
inverse.rs

1//! inverse implementation
2
3use crate::*;
4use super::exp2;
5use arithmetic::decimal::get_power_of_ten_u64;
6
7/// Implementation of inverse: (1/n)
8pub(crate) fn impl_inverse_uint_scale(n: &BigUint, scale: i64, ctx: &Context) -> BigDecimal {
9
10    if let Some(small_pow_ten) = n.to_u64().and_then(get_power_of_ten_u64) {
11        // optimized inversion for small power of ten:
12        //  1/10^{pow - scale} = 10^{scale - pow}
13
14        // create bigint with requested precision
15        let prec = ctx.precision().get();
16        let inv_int = BigInt::from(10u8).pow(prec as u32 - 1);
17
18        // increase inverted scale by requested precision
19        let inv_scale = small_pow_ten as i64 - scale + prec as i64 - 1;
20
21        return BigDecimal::new(inv_int, inv_scale);
22    }
23
24    // use f64 approximation to guess initial inverse
25    let guess = n.to_f64()
26        .map(|f| 1.0 / f)
27        .filter(|f| f.is_finite())
28        .and_then(BigDecimal::from_f64)
29        .map(|mut d| { d.scale -= scale; d })
30        .unwrap_or_else(
31            // couldn't use floating point, so just approximate with number of bits
32            || make_inv_guess(n.bits(), scale));
33
34    let max_precision = ctx.precision().get();
35
36    let s = BigDecimal::new(BigInt::from_biguint(Sign::Plus, n.clone()), scale);
37    let two = BigDecimal::from(2);
38
39    let next_iteration = move |r: BigDecimal| {
40        let tmp = &two - &s * &r;
41        r * tmp
42    };
43
44    // calculate first iteration
45    let mut running_result = next_iteration(guess);
46    if true {
    if !!running_result.is_zero() {
        {
            ::std::rt::panic_fmt(format_args!("Zero detected in inverse calculation of {0}e{1}",
                    n, -scale));
        }
    };
};debug_assert!(!running_result.is_zero(), "Zero detected in inverse calculation of {}e{}", n, -scale);
47
48    let mut prev_result = BigDecimal::one();
49    let mut result = BigDecimal::zero();
50
51    // TODO: Prove that we don't need to arbitrarily limit iterations
52    // and that convergence can be calculated
53    while prev_result != result {
54        // store current result to test for convergence
55        prev_result = result;
56
57        // calculate next iteration
58        running_result = next_iteration(running_result).with_prec(max_precision + 2);
59
60        // 'result' has clipped precision, 'running_result' has full precision
61        result = if running_result.digits() > max_precision {
62            running_result.with_precision_round(ctx.precision(), ctx.rounding_mode())
63        } else {
64            running_result.clone()
65        };
66    }
67
68    return result;
69}
70
71
72/// guess inverse based on the number of bits in the integer and decimal's scale
73fn make_inv_guess(bit_count: u64, scale: i64) -> BigDecimal {
74    // scale by ln(2)
75    let magic_factor = stdlib::f64::consts::LN_2;
76
77    let bit_count = bit_count as f64;
78    let initial_guess = magic_factor * exp2(-bit_count);
79    if initial_guess.is_finite() && initial_guess != 0.0 {
80        if let Ok(mut result) = BigDecimal::try_from(initial_guess) {
81            result.scale -= scale;
82            return result;
83        }
84    }
85
86    // backup guess for out-of-range integers
87
88    let approx_scale = bit_count * stdlib::f64::consts::LOG10_2;
89    let approx_scale_int = approx_scale.trunc();
90    let approx_scale_frac = approx_scale - approx_scale_int;
91
92    let recip = libm::exp10(-approx_scale_frac);
93    let mut res = BigDecimal::from_f32((magic_factor * recip) as f32).unwrap();
94    res.scale += approx_scale_int as i64;
95    res.scale -= scale;
96    return res;
97}
98
99
100#[cfg(test)]
101mod test_make_inv_guess {
102    use super::*;
103    use paste::paste;
104
105    macro_rules! impl_case {
106        ( $bin_count:literal, -$scale:literal => $expected:literal ) => {
107            paste! { impl_case!( [< case_ $bin_count _n $scale >]: $bin_count, -$scale => $expected); }
108        };
109        ( $bin_count:literal, $scale:literal => $expected:literal ) => {
110            paste! { impl_case!( [< case_ $bin_count _ $scale >]: $bin_count, $scale => $expected); }
111        };
112        ( $name:ident: $bin_count:expr, $scale:expr => $expected:literal ) => {
113            impl_case!($name: $bin_count, $scale, prec=5 => $expected);
114        };
115        ( $name:ident: $bin_count:expr, $scale:expr, prec=$prec:literal => $expected:literal ) => {
116            #[test]
117            fn $name() {
118                let guess = make_inv_guess($bin_count, $scale);
119                let expected: BigDecimal = $expected.parse().unwrap();
120                assert_eq!(guess.with_prec($prec), expected.with_prec($prec));
121            }
122        };
123    }
124
125    impl_case!(0, 0 => "0.69315");
126    impl_case!(1, 0 => "0.34657");
127    impl_case!(2, 0 => "0.17329");
128    impl_case!(2, 1 => "1.7329");
129
130    // 1 / (2^3 * 10^5) ~
131    impl_case!(3, -5 => "8.6643e-07");
132
133    // 2^-20
134    impl_case!(20, 0 => "6.6104e-07");
135    impl_case!(20, -900 => "6.6104E-907");
136    impl_case!(20, 800 => "6.6104E+793");
137
138    impl_case!(40, 10000 => "6.3041E+9987");
139
140    impl_case!(70, -5 => "5.8712e-27");
141    impl_case!(70, 5 => "5.8712e-17");
142    impl_case!(70, 50 => "5.8712e+28");
143
144    impl_case!(888, -300 => "3.3588E-568");
145    impl_case!(888, -19 => "3.3588E-287");
146    impl_case!(888, 0 => "3.3588E-268");
147    impl_case!(888, 270 => "335.88");
148
149    impl_case!(1022, 10 => "1.5423e-298");
150    impl_case!(1022, 308 => "1.5423");
151
152    impl_case!(1038, 316 => "2353.4");
153
154    impl_case!(case_31028_n659: 31028, -659 => "3.0347E-10000");
155    impl_case!(case_31028_0: 31028, 0 => "3.0347E-9341");
156    impl_case!(case_31028_1: 31028, 1 => "3.0347E-9340");
157    impl_case!(case_31028_9340: 31028, 9340 => ".30347");
158    impl_case!(case_31028_10000: 31028, 10000 => "3.0347E+659");
159
160    // impl_case!(case_max: u64::MAX, 270 => "335.88");
161}
162
163#[cfg(test)]
164mod test {
165    use super::*;
166    use paste::paste;
167    use stdlib::num::NonZeroU64;
168
169    #[test]
170    fn test_inverse_35543972957198043e291() {
171        let v = vec![
172            0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
173            0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
174            2324389888, 849200558
175        ];
176        let x = BigInt::new(Sign::Minus, v);
177        let d = BigDecimal::from(x);
178        let expected = "-2.813416500187520746852694701086705659180043761702417561798711758892800449936819185796527214192677476E-308".parse().unwrap();
179        assert_eq!(d.inverse(), expected);
180
181        assert_eq!(d.neg().inverse(), expected.neg());
182    }
183
184    macro_rules! impl_case {
185        ($name:ident: $prec:literal, $round:ident => $expected:literal) => {
186            #[test]
187            fn $name() {
188                let n = test_input();
189                let prec = NonZeroU64::new($prec).unwrap();
190                let rounding = RoundingMode::$round;
191                let ctx = Context::new(prec, rounding);
192
193                let result = n.inverse_with_context(&ctx);
194
195                let expected = $expected.parse().unwrap();
196                assert_eq!(&result, &expected);
197
198                let product = result * &n;
199                let epsilon = BigDecimal::new(BigInt::one(), $prec - 1);
200                let diff = (BigDecimal::one() - &product).abs();
201                assert!(diff < epsilon);
202            }
203        };
204        (prec=$prec:literal, round=$round:ident => $expected:literal) => {
205            paste! {
206                #[test]
207                fn [< case_prec $prec _round_ $round:lower >] () {
208                    let n = test_input();
209                    let prec = NonZeroU64::new($prec).unwrap();
210                    let rounding = RoundingMode::$round;
211                    let ctx = Context::new(prec, rounding);
212
213                    let result = n.inverse_with_context(&ctx);
214
215                    let expected = $expected.parse().unwrap();
216                    assert_eq!(&result, &expected);
217                    assert_eq!(&result.scale, &expected.scale);
218                }
219            }
220        };
221        (prec=$prec:literal, round=$($round:ident),+ => $expected:literal) => {
222            $( impl_case!(prec=$prec, round=$round => $expected); )*
223        };
224    }
225
226    mod invert_one {
227        use super::*;
228
229        fn test_input() -> BigDecimal {
230            1u8.into()
231        }
232
233        impl_case!(prec=1, round=Up,Down => "1");
234        impl_case!(prec=2, round=Up,Down => "1.0");
235        impl_case!(prec=7, round=Up,Down => "1.000000");
236    }
237
238    mod invert_n1d00 {
239        use super::*;
240
241        fn test_input() -> BigDecimal {
242            "-1.00".parse().unwrap()
243        }
244
245        impl_case!(prec=1, round=Up,Down => "-1");
246        impl_case!(prec=5, round=Up,Down => "-1.0000");
247    }
248
249    mod invert_n1000en8 {
250        use super::*;
251
252        fn test_input() -> BigDecimal {
253            "1000e-8".parse().unwrap()
254        }
255
256        impl_case!(prec=1, round=Up,Down => "1e5");
257        impl_case!(prec=5, round=Up,Down => "10000e1");
258        impl_case!(prec=6, round=Up,Down => "100000");
259        impl_case!(prec=8, round=Up,Down => "100000.00");
260    }
261
262    mod invert_seven {
263        use super::*;
264
265        fn test_input() -> BigDecimal {
266            BigDecimal::from(7u8)
267        }
268
269        impl_case!(case_prec10_round_down: 10, Down => "0.1428571428");
270        impl_case!(case_prec10_round_up: 10, Up => "0.1428571429");
271
272        impl_case!(case_prec11_round_ceiling: 11, Ceiling => "0.14285714286");
273    }
274
275    mod invert_ten {
276        use super::*;
277
278        fn test_input() -> BigDecimal {
279            10u8.into()
280        }
281
282        impl_case!(case_prec1_round_down: 1, Down => "0.1");
283        impl_case!(case_prec2_round_down: 2, Down => "0.10");
284        impl_case!(prec=10, round=Up, Down => "0.1000000000");
285    }
286
287    mod invert_n3242342d34324 {
288        use super::*;
289
290        fn test_input() -> BigDecimal {
291            "-3242342.34324".parse().unwrap()
292        }
293
294        // note: floor ceiling wrong
295        impl_case!(prec=50, round=Up, Ceiling => "-3.0841900519385698894827476971712670726697831310897E-7");
296        impl_case!(prec=50, round=Down, Floor => "-3.0841900519385698894827476971712670726697831310896E-7");
297    }
298
299
300    #[test]
301    fn inv_random_number() {
302        let n = BigDecimal::try_from(0.08121970592310568).unwrap();
303
304        let ctx = Context::new(NonZeroU64::new(40).unwrap(), RoundingMode::Down);
305        let i = n.inverse_with_context(&ctx);
306        assert_eq!(&i, &"12.31228294456944530942557443718279245563".parse().unwrap());
307
308        let product = i * &n;
309        assert!(BigDecimal::one() - &product < "1e-39".parse().unwrap());
310    }
311
312    #[cfg(property_tests)]
313    mod prop {
314        use super::*;
315        use proptest::*;
316        use num_traits::FromPrimitive;
317
318        proptest! {
319
320            #[test]
321            fn inverse_multiplies_to_one(f: f64, prec in 1..100u64) {
322                // ignore non-normal numbers
323                prop_assume!(f.is_normal());
324                prop_assume!(f != 0.0);
325
326                let n = BigDecimal::from_f64(f).unwrap();
327
328                let ctx = Context::new(NonZeroU64::new(prec).unwrap(), RoundingMode::Up);
329                let i = n.inverse_with_context(&ctx);
330                let product = &i * &n;
331
332                // accurate to precision minus one (due to rounding)
333                let epsilon = BigDecimal::new(1.into(), prec as i64 - 1);
334                let diff_from_one = BigDecimal::one() - &product;
335
336                prop_assert!(diff_from_one.abs() < epsilon, "{} >= {}", diff_from_one.abs(), epsilon);
337            }
338        }
339    }
340}