rpn_cli/util/
number.rs

1use num::{BigInt, BigRational, FromPrimitive, Signed, ToPrimitive, Zero};
2use once_cell::sync::Lazy;
3use std::borrow::Cow;
4
5pub fn create_ratio(numer: i128, denom: i128) -> BigRational {
6    let numer = BigInt::from(numer);
7    let denom = BigInt::from(denom);
8    BigRational::new(numer, denom)
9}
10
11pub fn format_ratio(number: &BigRational) -> String {
12    static LENGTH: Lazy<usize> = Lazy::new(|| measure_sqrts());
13    format_inner(number, *LENGTH)
14}
15
16fn format_inner(number: &BigRational, length: usize) -> String {
17    // Copied from big_rational_str crate; removed "0.(6)" formatting;
18    // added maximum fraction length limit to avoid infinite loops.
19    let absolute = if number.is_negative() {
20        Cow::Owned(number.abs())
21    } else {
22        Cow::Borrowed(number)
23    };
24    let mut fraction = String::new();
25    let mut remainder = absolute.numer() % absolute.denom();
26    while !remainder.is_zero() && fraction.len() < length {
27        remainder *= BigInt::from(10);
28        fraction.push_str(&(remainder.clone() / absolute.denom()).to_string());
29        remainder %= absolute.denom();
30    }
31    let mut integer = if number.is_negative() {
32        String::from("-")
33    } else {
34        String::new()
35    };
36    integer.push_str(&(absolute.numer() / absolute.denom()).to_string());
37    if fraction.is_empty() {
38        integer
39    } else {
40        format!("{integer}.{fraction}")
41    }
42}
43
44fn measure_sqrts() -> usize {
45    let numbers = vec![2, 3, 5, 6, 7, 8];
46    numbers.iter()
47        .map(|n| measure_sqrt(*n))
48        .max()
49        .unwrap_or_default()
50}
51
52fn measure_sqrt(number: i128) -> usize {
53    create_ratio(number, 1).to_f64()
54        .map(f64::sqrt)
55        .and_then(BigRational::from_f64)
56        .map(|x| format_inner(&x, 200))
57        .map(measure_fraction)
58        .unwrap_or_default()
59}
60
61fn measure_fraction(number: String) -> usize {
62    if let Some(period) = number.find('.') {
63        number.len().checked_sub(period).unwrap_or_default()
64    } else {
65        0
66    }
67}