1use crate::*;
4use super::exp2;
5use arithmetic::decimal::get_power_of_ten_u64;
6
7pub(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 let prec = ctx.precision().get();
16 let inv_int = BigInt::from(10u8).pow(prec as u32 - 1);
17
18 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 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 || 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 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 while prev_result != result {
54 prev_result = result;
56
57 running_result = next_iteration(running_result).with_prec(max_precision + 2);
59
60 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
72fn make_inv_guess(bit_count: u64, scale: i64) -> BigDecimal {
74 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 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 impl_case!(3, -5 => "8.6643e-07");
132
133 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 }
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 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 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 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}