1use std::fmt::{Debug, Display};
6use std::ops::{Div, Mul, Neg, Sub};
7use std::sync::LazyLock;
8use thiserror::Error;
9
10pub type FixedDecimal = crate::math_dashu::Decimal;
11
12pub static ZERO: LazyLock<FixedDecimal> = LazyLock::new(|| FixedDecimal::from(0u64));
13pub static MINUS_ONE: LazyLock<FixedDecimal> = LazyLock::new(|| FixedDecimal::from(-1i64));
14pub static ONE: LazyLock<FixedDecimal> = LazyLock::new(|| FixedDecimal::from(1u64));
15
16#[derive(Debug, Error)]
17pub enum Error {
18 #[error("error in regex")]
19 RegexFailure(#[from] regex::Error),
20
21 #[error("string contained a nul byte")]
22 NulFailure(#[from] std::ffi::NulError),
23}
24
25pub const DEFAULT_PRECISION: u64 = 34;
26
27pub trait FixedPrecision:
28 Neg + Mul + Div + Sub + Display + Clone + PartialEq + PartialOrd + Debug + From<u64> + From<i64>
29{
30 fn new(precision: u64) -> Self;
32
33 fn from_str(s: &str, precision: u64) -> Result<Self, Error>;
35
36 fn precision(&self) -> u64;
38
39 fn exp(&self) -> Self;
42
43 fn ln(&self) -> Self;
47
48 fn pow(&self, y: &Self) -> Self;
50
51 fn exp_cmp(&self, max_n: u64, bound_self: i64, compare: &Self) -> ExpCmpOrdering;
53
54 #[must_use]
56 fn round(&self) -> Self;
57
58 #[must_use]
60 fn floor(&self) -> Self;
61
62 #[must_use]
64 fn ceil(&self) -> Self;
65
66 #[must_use]
68 fn trunc(&self) -> Self;
69}
70
71#[derive(Debug, Clone, PartialEq)]
72pub enum ExpOrdering {
73 GT,
74 LT,
75 UNKNOWN,
76}
77
78impl From<&str> for ExpOrdering {
79 fn from(s: &str) -> Self {
80 match s {
81 "GT" => ExpOrdering::GT,
82 "LT" => ExpOrdering::LT,
83 _ => ExpOrdering::UNKNOWN,
84 }
85 }
86}
87
88#[derive(Debug, Clone, PartialEq)]
89pub struct ExpCmpOrdering {
90 pub iterations: u64,
91 pub estimation: ExpOrdering,
92 pub approx: FixedDecimal,
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98 use dashu_base::Abs;
99 use proptest::prelude::Strategy;
100 use proptest::proptest;
101 use std::fs::File;
102 use std::io::BufRead;
103 use std::path::PathBuf;
104
105 #[test]
106 fn test_fixed_precision() {
107 let fp: FixedDecimal = FixedDecimal::new(34);
108 assert_eq!(fp.precision(), 34);
109 assert_eq!(fp.to_string(), "0.0000000000000000000000000000000000");
110 }
111
112 #[test]
113 fn test_fixed_precision_eq() {
114 let fp1: FixedDecimal = FixedDecimal::new(34);
115 let fp2: FixedDecimal = FixedDecimal::new(34);
116 assert_eq!(fp1, fp2);
117 }
118
119 #[test]
120 fn test_fixed_precision_from_str() {
121 let fp: FixedDecimal =
122 FixedDecimal::from_str("1234567890123456789012345678901234", 34).unwrap();
123 assert_eq!(fp.precision(), 34);
124 assert_eq!(fp.to_string(), "0.1234567890123456789012345678901234");
125
126 let fp: FixedDecimal =
127 FixedDecimal::from_str("-1234567890123456789012345678901234", 30).unwrap();
128 assert_eq!(fp.precision(), 30);
129 assert_eq!(fp.to_string(), "-1234.567890123456789012345678901234");
130
131 let fp: FixedDecimal =
132 FixedDecimal::from_str("-1234567890123456789012345678901234", 34).unwrap();
133 assert_eq!(fp.precision(), 34);
134 assert_eq!(fp.to_string(), "-0.1234567890123456789012345678901234");
135 }
136
137 #[test]
138 fn test_fixed_precision_exp() {
139 let fp: FixedDecimal = FixedDecimal::from(1u64);
140 assert_eq!(fp.to_string(), "1.0000000000000000000000000000000000");
141 let exp_fp = fp.exp();
142 assert_eq!(exp_fp.to_string(), "2.7182818284590452353602874043083282");
143 }
144
145 #[test]
146 fn test_fixed_precision_mul() {
147 let fp1: FixedDecimal =
148 FixedDecimal::from_str("52500000000000000000000000000000000", 34).unwrap();
149 let fp2: FixedDecimal =
150 FixedDecimal::from_str("43000000000000000000000000000000000", 34).unwrap();
151 let fp3 = &fp1 * &fp2;
152 assert_eq!(fp3.to_string(), "22.5750000000000000000000000000000000");
153 let fp4 = fp1 * fp2;
154 assert_eq!(fp4.to_string(), "22.5750000000000000000000000000000000");
155 }
156
157 #[test]
158 fn test_fixed_precision_div() {
159 let fp1: FixedDecimal = FixedDecimal::from_str("1", 34).unwrap();
160 let fp2: FixedDecimal = FixedDecimal::from_str("10", 34).unwrap();
161 let fp3 = &fp1 / &fp2;
162 assert_eq!(fp3.to_string(), "0.1000000000000000000000000000000000");
163 let fp4 = fp1 / fp2;
164 assert_eq!(fp4.to_string(), "0.1000000000000000000000000000000000");
165 }
166
167 #[test]
168 fn test_fixed_precision_sub() {
169 let fp1: FixedDecimal = FixedDecimal::from_str("1", 34).unwrap();
170 assert_eq!(fp1.to_string(), "0.0000000000000000000000000000000001");
171 let fp2: FixedDecimal = FixedDecimal::from_str("10", 34).unwrap();
172 assert_eq!(fp2.to_string(), "0.0000000000000000000000000000000010");
173 let fp3 = &fp1 - &fp2;
174 assert_eq!(fp3.to_string(), "-0.0000000000000000000000000000000009");
175 let fp4 = fp1 - fp2;
176 assert_eq!(fp4.to_string(), "-0.0000000000000000000000000000000009");
177 }
178
179 #[test]
180 fn test_fixed_precision_round() {
181 let fp1: FixedDecimal =
182 FixedDecimal::from_str("11234567890123456789012345678901234", 34).unwrap();
183 assert_eq!(
184 fp1.round().to_string(),
185 "1.0000000000000000000000000000000000"
186 );
187 let fp2: FixedDecimal =
188 FixedDecimal::from_str("14999999999999999999999999999999999", 34).unwrap();
189 assert_eq!(
190 fp2.round().to_string(),
191 "1.0000000000000000000000000000000000"
192 );
193 let fp3: FixedDecimal =
194 FixedDecimal::from_str("15000000000000000000000000000000000", 34).unwrap();
195 assert_eq!(
196 fp3.round().to_string(),
197 "2.0000000000000000000000000000000000"
198 );
199 let fp4: FixedDecimal = FixedDecimal::from_str("1500", 3).unwrap();
200 assert_eq!(fp4.round().to_string(), "2.000");
201 let fp5: FixedDecimal = FixedDecimal::from_str("1499", 3).unwrap();
202 assert_eq!(fp5.round().to_string(), "1.000");
203 let fp6: FixedDecimal =
204 FixedDecimal::from_str("-11234567890123456789012345678901234", 34).unwrap();
205 assert_eq!(
206 fp6.round().to_string(),
207 "-1.0000000000000000000000000000000000"
208 );
209 let fp2: FixedDecimal =
210 FixedDecimal::from_str("-14999999999999999999999999999999999", 34).unwrap();
211 assert_eq!(
212 fp2.round().to_string(),
213 "-1.0000000000000000000000000000000000"
214 );
215 let fp3: FixedDecimal =
216 FixedDecimal::from_str("-15000000000000000000000000000000000", 34).unwrap();
217 assert_eq!(
218 fp3.round().to_string(),
219 "-2.0000000000000000000000000000000000"
220 );
221 let fp4: FixedDecimal = FixedDecimal::from_str("-1500", 3).unwrap();
222 assert_eq!(fp4.round().to_string(), "-2.000");
223 let fp5: FixedDecimal = FixedDecimal::from_str("-1499", 3).unwrap();
224 assert_eq!(fp5.round().to_string(), "-1.000");
225 let fp6: FixedDecimal = FixedDecimal::from_str("1000", 3).unwrap();
226 assert_eq!(fp6.round().to_string(), "1.000");
227 let fp7: FixedDecimal = FixedDecimal::from_str("-1000", 3).unwrap();
228 assert_eq!(fp7.round().to_string(), "-1.000");
229 }
230
231 #[test]
232 fn test_fixed_precision_floor() {
233 let fp1: FixedDecimal =
234 FixedDecimal::from_str("11234567890123456789012345678901234", 34).unwrap();
235 assert_eq!(
236 fp1.floor().to_string(),
237 "1.0000000000000000000000000000000000"
238 );
239 let fp2: FixedDecimal =
240 FixedDecimal::from_str("14999999999999999999999999999999999", 34).unwrap();
241 assert_eq!(
242 fp2.floor().to_string(),
243 "1.0000000000000000000000000000000000"
244 );
245 let fp3: FixedDecimal =
246 FixedDecimal::from_str("15000000000000000000000000000000000", 34).unwrap();
247 assert_eq!(
248 fp3.floor().to_string(),
249 "1.0000000000000000000000000000000000"
250 );
251 let fp4: FixedDecimal = FixedDecimal::from_str("1500", 3).unwrap();
252 assert_eq!(fp4.floor().to_string(), "1.000");
253 let fp5: FixedDecimal = FixedDecimal::from_str("1499", 3).unwrap();
254 assert_eq!(fp5.floor().to_string(), "1.000");
255 let fp6: FixedDecimal =
256 FixedDecimal::from_str("-11234567890123456789012345678901234", 34).unwrap();
257 assert_eq!(
258 fp6.floor().to_string(),
259 "-2.0000000000000000000000000000000000"
260 );
261 let fp2: FixedDecimal =
262 FixedDecimal::from_str("-14999999999999999999999999999999999", 34).unwrap();
263 assert_eq!(
264 fp2.floor().to_string(),
265 "-2.0000000000000000000000000000000000"
266 );
267 let fp3: FixedDecimal =
268 FixedDecimal::from_str("-15000000000000000000000000000000000", 34).unwrap();
269 assert_eq!(
270 fp3.floor().to_string(),
271 "-2.0000000000000000000000000000000000"
272 );
273 let fp4: FixedDecimal = FixedDecimal::from_str("-1500", 3).unwrap();
274 assert_eq!(fp4.floor().to_string(), "-2.000");
275 let fp5: FixedDecimal = FixedDecimal::from_str("-1499", 3).unwrap();
276 assert_eq!(fp5.floor().to_string(), "-2.000");
277 let fp6: FixedDecimal = FixedDecimal::from_str("1000", 3).unwrap();
278 assert_eq!(fp6.floor().to_string(), "1.000");
279 let fp7: FixedDecimal = FixedDecimal::from_str("-1000", 3).unwrap();
280 assert_eq!(fp7.floor().to_string(), "-1.000");
281 }
282
283 #[test]
284 fn test_fixed_precision_ceil() {
285 let fp1: FixedDecimal =
286 FixedDecimal::from_str("11234567890123456789012345678901234", 34).unwrap();
287 assert_eq!(
288 fp1.ceil().to_string(),
289 "2.0000000000000000000000000000000000"
290 );
291 let fp2: FixedDecimal =
292 FixedDecimal::from_str("14999999999999999999999999999999999", 34).unwrap();
293 assert_eq!(
294 fp2.ceil().to_string(),
295 "2.0000000000000000000000000000000000"
296 );
297 let fp3: FixedDecimal =
298 FixedDecimal::from_str("15000000000000000000000000000000000", 34).unwrap();
299 assert_eq!(
300 fp3.ceil().to_string(),
301 "2.0000000000000000000000000000000000"
302 );
303 let fp4: FixedDecimal = FixedDecimal::from_str("1500", 3).unwrap();
304 assert_eq!(fp4.ceil().to_string(), "2.000");
305 let fp5: FixedDecimal = FixedDecimal::from_str("1499", 3).unwrap();
306 assert_eq!(fp5.ceil().to_string(), "2.000");
307 let fp6: FixedDecimal =
308 FixedDecimal::from_str("-11234567890123456789012345678901234", 34).unwrap();
309 assert_eq!(
310 fp6.ceil().to_string(),
311 "-1.0000000000000000000000000000000000"
312 );
313 let fp2: FixedDecimal =
314 FixedDecimal::from_str("-14999999999999999999999999999999999", 34).unwrap();
315 assert_eq!(
316 fp2.ceil().to_string(),
317 "-1.0000000000000000000000000000000000"
318 );
319 let fp3: FixedDecimal =
320 FixedDecimal::from_str("-15000000000000000000000000000000000", 34).unwrap();
321 assert_eq!(
322 fp3.ceil().to_string(),
323 "-1.0000000000000000000000000000000000"
324 );
325 let fp4: FixedDecimal = FixedDecimal::from_str("-1500", 3).unwrap();
326 assert_eq!(fp4.ceil().to_string(), "-1.000");
327 let fp5: FixedDecimal = FixedDecimal::from_str("-1499", 3).unwrap();
328 assert_eq!(fp5.ceil().to_string(), "-1.000");
329 let fp6: FixedDecimal = FixedDecimal::from_str("1000", 3).unwrap();
330 assert_eq!(fp6.ceil().to_string(), "1.000");
331 let fp7: FixedDecimal = FixedDecimal::from_str("-1000", 3).unwrap();
332 assert_eq!(fp7.ceil().to_string(), "-1.000");
333 }
334
335 #[test]
336 fn test_fixed_precision_trunc() {
337 let fp1: FixedDecimal =
338 FixedDecimal::from_str("11234567890123456789012345678901234", 34).unwrap();
339 assert_eq!(
340 fp1.trunc().to_string(),
341 "1.0000000000000000000000000000000000"
342 );
343 let fp2: FixedDecimal =
344 FixedDecimal::from_str("14999999999999999999999999999999999", 34).unwrap();
345 assert_eq!(
346 fp2.trunc().to_string(),
347 "1.0000000000000000000000000000000000"
348 );
349 let fp3: FixedDecimal =
350 FixedDecimal::from_str("15000000000000000000000000000000000", 34).unwrap();
351 assert_eq!(
352 fp3.trunc().to_string(),
353 "1.0000000000000000000000000000000000"
354 );
355 let fp4: FixedDecimal = FixedDecimal::from_str("1500", 3).unwrap();
356 assert_eq!(fp4.trunc().to_string(), "1.000");
357 let fp5: FixedDecimal = FixedDecimal::from_str("1499", 3).unwrap();
358 assert_eq!(fp5.trunc().to_string(), "1.000");
359 let fp6: FixedDecimal =
360 FixedDecimal::from_str("-11234567890123456789012345678901234", 34).unwrap();
361 assert_eq!(
362 fp6.trunc().to_string(),
363 "-1.0000000000000000000000000000000000"
364 );
365 let fp2: FixedDecimal =
366 FixedDecimal::from_str("-14999999999999999999999999999999999", 34).unwrap();
367 assert_eq!(
368 fp2.trunc().to_string(),
369 "-1.0000000000000000000000000000000000"
370 );
371 let fp3: FixedDecimal =
372 FixedDecimal::from_str("-15000000000000000000000000000000000", 34).unwrap();
373 assert_eq!(
374 fp3.trunc().to_string(),
375 "-1.0000000000000000000000000000000000"
376 );
377 let fp4: FixedDecimal = FixedDecimal::from_str("-1500", 3).unwrap();
378 assert_eq!(fp4.trunc().to_string(), "-1.000");
379 let fp5: FixedDecimal = FixedDecimal::from_str("-1499", 3).unwrap();
380 assert_eq!(fp5.trunc().to_string(), "-1.000");
381 let fp6: FixedDecimal = FixedDecimal::from_str("1000", 3).unwrap();
382 assert_eq!(fp6.trunc().to_string(), "1.000");
383 let fp7: FixedDecimal = FixedDecimal::from_str("-1000", 3).unwrap();
384 assert_eq!(fp7.trunc().to_string(), "-1.000");
385 }
386
387 #[test]
388 fn golden_tests() {
389 let mut data_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
390 data_path.push("tests/data/golden_tests.txt");
391
392 let file = File::open(data_path).expect("golden_tests.txt: file not found");
394 let reader = std::io::BufReader::new(file);
395
396 let mut data_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
398 data_path.push("tests/data/golden_tests_result.txt");
399 let file = File::open(data_path).expect("golden_tests_result.txt: file not found");
400 let result_reader = std::io::BufReader::new(file);
401
402 let one: FixedDecimal = FixedDecimal::from(1u64);
403 let ten: FixedDecimal = FixedDecimal::from(10u64);
404 let f: FixedDecimal = &one / &ten;
405 assert_eq!(f.to_string(), "0.1000000000000000000000000000000000");
406
407 for (test_line, result_line) in reader.lines().zip(result_reader.lines()) {
408 let test_line = test_line.expect("failed to read line");
409 let mut parts = test_line.split_whitespace();
411 let x = FixedDecimal::from_str(parts.next().unwrap(), DEFAULT_PRECISION)
412 .expect("failed to parse x");
413 let a = FixedDecimal::from_str(parts.next().unwrap(), DEFAULT_PRECISION)
414 .expect("failed to parse a");
415 let b = FixedDecimal::from_str(parts.next().unwrap(), DEFAULT_PRECISION)
416 .expect("failed to parse b");
417 let result_line = result_line.expect("failed to read line");
418 let mut result_parts = result_line.split_whitespace();
420 let expected_exp_x = result_parts.next().expect("expected_exp_x not found");
421 let expected_ln_a = result_parts.next().expect("expected_ln_a not found");
422 let expected_threshold_b = result_parts.next().expect("expected_threshold_b not found");
423 let expected_approx_exp = result_parts.next().expect("expected_approx_exp not found");
424 let expected_estimation =
425 ExpOrdering::from(result_parts.next().expect("expected_estimation not found"));
426 let expected_iterations = result_parts.next().expect("expected_iterations not found");
427
428 let exp_x = x.exp();
430 assert_eq!(exp_x.to_string(), expected_exp_x);
431
432 let ln_a = a.ln();
434 assert_eq!((-ln_a).to_string(), expected_ln_a);
435
436 let c = &one - &f;
438 assert_eq!(c.to_string(), "0.9000000000000000000000000000000000");
439 let threshold_b = c.pow(&b);
440 assert_eq!(
441 (&one - &threshold_b).to_string(),
442 expected_threshold_b,
443 "(1 - f) *** b failed to match! - (1 - f)={}, b={}",
444 &c,
445 &b
446 );
447
448 let c = &one - &f;
452 let temp = c.ln();
453 let alpha = &b * &temp;
454 let alpha = -alpha;
455 let q_ = &one - &a;
456 let q = &one / &q_;
457 let res = alpha.exp_cmp(1000, 3, &q);
458
459 let threshold = &one - &threshold_b;
467 if a < threshold && res.estimation != ExpOrdering::LT {
468 panic!(
469 "wrong result should be leader {} should be more like {}",
470 &temp, threshold
471 );
472 }
473
474 if a >= threshold && res.estimation != ExpOrdering::GT {
475 panic!(
476 "wrong result should not be leader {} should be more like {}",
477 &temp, threshold
478 );
479 }
480
481 assert_eq!(res.approx.to_string(), expected_approx_exp);
482 assert_eq!(res.estimation, expected_estimation);
483 assert_eq!(res.iterations.to_string(), expected_iterations);
484 }
485 }
486
487 #[test]
488 #[should_panic(expected = "ln of a value in (-inf,0] is undefined")]
489 fn ln_of_0_should_be_undefined() {
490 ZERO.ln();
491 }
492
493 #[test]
494 #[should_panic(expected = "ln of a value in (-inf,0] is undefined")]
495 fn ln_of_negative_should_be_undefined() {
496 MINUS_ONE.ln();
497 }
498
499 #[test]
500 fn pow_of_zero_to_any_positive_power_should_be_zero() {
501 proptest!(|(y in 1u64..=u64::MAX)| {
502 assert_eq!(ZERO.pow(&FixedDecimal::from(y)), *ZERO);
503 });
504 }
505
506 #[test]
507 #[should_panic(expected = "zero to a negative power is undefined")]
508 fn pow_of_zero_to_neg_power_should_be_undefined() {
509 let y = FixedDecimal::from(-1i64);
510 ZERO.pow(&y);
511 }
512
513 #[test]
514 fn pow_of_any_to_power_0_should_be_1() {
515 proptest!(|(x in i64::MIN..=i64::MAX)| {
516 assert_eq!(FixedDecimal::from(x).pow(&*ZERO), *ONE);
517 });
518 }
519
520 #[test]
521 fn pow_of_any_to_power_1_should_be_same() {
522 proptest!(|(x in i64::MIN..=i64::MAX)| {
523 assert_eq!(FixedDecimal::from(x).pow(&*ONE), FixedDecimal::from(x));
524 });
525 }
526
527 #[test]
528 fn pow_to_positive_times_pow_to_negative_should_be_1() {
529 let epsilon = FixedDecimal::from_str("1000000000000000000", 34).unwrap();
530 proptest!(|(x in (-5i64..=5i64).prop_filter("Exclude zero", |&x| x != 0), y in 1i64..=25i64)| {
531 let x = FixedDecimal::from(x);
532 let y = FixedDecimal::from(y);
533 let minus_y = -&y;
534 let x_to_y = x.pow(&y);
535 let x_to_minus_y = x.pow(&minus_y);
536 let result = &x_to_y * &x_to_minus_y;
537 let diff = (&result - &*ONE).abs();
538 assert!(diff <= epsilon);
540 });
541 }
542}