Skip to main content

bigdecimal/arithmetic/
inverse.rs

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