rustpython_common/
int.rs

1use bstr::ByteSlice;
2use malachite_base::{num::conversion::traits::RoundingInto, rounding_modes::RoundingMode};
3use malachite_bigint::{BigInt, BigUint, Sign};
4use malachite_q::Rational;
5use num_traits::{One, ToPrimitive, Zero};
6
7pub fn true_div(numerator: &BigInt, denominator: &BigInt) -> f64 {
8    let rational = Rational::from_integers_ref(numerator.into(), denominator.into());
9    match rational.rounding_into(RoundingMode::Nearest) {
10        // returned value is $t::MAX but still less than the original
11        (val, std::cmp::Ordering::Less) if val == f64::MAX => f64::INFINITY,
12        // returned value is $t::MIN but still greater than the original
13        (val, std::cmp::Ordering::Greater) if val == f64::MIN => f64::NEG_INFINITY,
14        (val, _) => val,
15    }
16}
17
18pub fn float_to_ratio(value: f64) -> Option<(BigInt, BigInt)> {
19    let sign = match std::cmp::PartialOrd::partial_cmp(&value, &0.0)? {
20        std::cmp::Ordering::Less => Sign::Minus,
21        std::cmp::Ordering::Equal => return Some((BigInt::zero(), BigInt::one())),
22        std::cmp::Ordering::Greater => Sign::Plus,
23    };
24    Rational::try_from(value).ok().map(|x| {
25        let (numer, denom) = x.into_numerator_and_denominator();
26        (
27            BigInt::from_biguint(sign, numer.into()),
28            BigUint::from(denom).into(),
29        )
30    })
31}
32
33pub fn bytes_to_int(lit: &[u8], mut base: u32) -> Option<BigInt> {
34    // split sign
35    let mut lit = lit.trim();
36    let sign = match lit.first()? {
37        b'+' => Some(Sign::Plus),
38        b'-' => Some(Sign::Minus),
39        _ => None,
40    };
41    if sign.is_some() {
42        lit = &lit[1..];
43    }
44
45    // split radix
46    let first = *lit.first()?;
47    let has_radix = if first == b'0' {
48        match base {
49            0 => {
50                if let Some(parsed) = lit.get(1).and_then(detect_base) {
51                    base = parsed;
52                    true
53                } else {
54                    if let [_first, ref others @ .., last] = lit {
55                        let is_zero =
56                            others.iter().all(|&c| c == b'0' || c == b'_') && *last == b'0';
57                        if !is_zero {
58                            return None;
59                        }
60                    }
61                    return Some(BigInt::zero());
62                }
63            }
64            16 => lit.get(1).map_or(false, |&b| matches!(b, b'x' | b'X')),
65            2 => lit.get(1).map_or(false, |&b| matches!(b, b'b' | b'B')),
66            8 => lit.get(1).map_or(false, |&b| matches!(b, b'o' | b'O')),
67            _ => false,
68        }
69    } else {
70        if base == 0 {
71            base = 10;
72        }
73        false
74    };
75    if has_radix {
76        lit = &lit[2..];
77        if lit.first()? == &b'_' {
78            lit = &lit[1..];
79        }
80    }
81
82    // remove zeroes
83    let mut last = *lit.first()?;
84    if last == b'0' {
85        let mut count = 0;
86        for &cur in &lit[1..] {
87            if cur == b'_' {
88                if last == b'_' {
89                    return None;
90                }
91            } else if cur != b'0' {
92                break;
93            };
94            count += 1;
95            last = cur;
96        }
97        let prefix_last = lit[count];
98        lit = &lit[count + 1..];
99        if lit.is_empty() && prefix_last == b'_' {
100            return None;
101        }
102    }
103
104    // validate
105    for c in lit {
106        let c = *c;
107        if !(c.is_ascii_alphanumeric() || c == b'_') {
108            return None;
109        }
110
111        if c == b'_' && last == b'_' {
112            return None;
113        }
114
115        last = c;
116    }
117    if last == b'_' {
118        return None;
119    }
120
121    // parse
122    let number = if lit.is_empty() {
123        BigInt::zero()
124    } else {
125        let uint = BigUint::parse_bytes(lit, base)?;
126        BigInt::from_biguint(sign.unwrap_or(Sign::Plus), uint)
127    };
128    Some(number)
129}
130
131#[inline]
132pub fn detect_base(c: &u8) -> Option<u32> {
133    let base = match c {
134        b'x' | b'X' => 16,
135        b'b' | b'B' => 2,
136        b'o' | b'O' => 8,
137        _ => return None,
138    };
139    Some(base)
140}
141
142// num-bigint now returns Some(inf) for to_f64() in some cases, so just keep that the same for now
143#[inline(always)]
144pub fn bigint_to_finite_float(int: &BigInt) -> Option<f64> {
145    int.to_f64().filter(|f| f.is_finite())
146}
147
148#[test]
149fn test_bytes_to_int() {
150    assert_eq!(bytes_to_int(&b"0b101"[..], 2).unwrap(), BigInt::from(5));
151    assert_eq!(bytes_to_int(&b"0x_10"[..], 16).unwrap(), BigInt::from(16));
152    assert_eq!(bytes_to_int(&b"0b"[..], 16).unwrap(), BigInt::from(11));
153    assert_eq!(bytes_to_int(&b"+0b101"[..], 2).unwrap(), BigInt::from(5));
154    assert_eq!(bytes_to_int(&b"0_0_0"[..], 10).unwrap(), BigInt::from(0));
155    assert_eq!(bytes_to_int(&b"09_99"[..], 0), None);
156    assert_eq!(bytes_to_int(&b"000"[..], 0).unwrap(), BigInt::from(0));
157    assert_eq!(bytes_to_int(&b"0_"[..], 0), None);
158    assert_eq!(bytes_to_int(&b"0_100"[..], 10).unwrap(), BigInt::from(100));
159}