Skip to main content

rustpython_common/
int.rs

1use malachite_base::{num::conversion::traits::RoundingInto, rounding_modes::RoundingMode};
2use malachite_bigint::{BigInt, BigUint, Sign};
3use malachite_q::Rational;
4use num_traits::{One, ToPrimitive, Zero};
5
6pub fn true_div(numerator: &BigInt, denominator: &BigInt) -> f64 {
7    let rational = Rational::from_integers_ref(numerator.into(), denominator.into());
8    match rational.rounding_into(RoundingMode::Nearest) {
9        // returned value is $t::MAX but still less than the original
10        (val, core::cmp::Ordering::Less) if val == f64::MAX => f64::INFINITY,
11        // returned value is $t::MIN but still greater than the original
12        (val, core::cmp::Ordering::Greater) if val == f64::MIN => f64::NEG_INFINITY,
13        (val, _) => val,
14    }
15}
16
17pub fn float_to_ratio(value: f64) -> Option<(BigInt, BigInt)> {
18    let sign = match core::cmp::PartialOrd::partial_cmp(&value, &0.0)? {
19        core::cmp::Ordering::Less => Sign::Minus,
20        core::cmp::Ordering::Equal => return Some((BigInt::zero(), BigInt::one())),
21        core::cmp::Ordering::Greater => Sign::Plus,
22    };
23    Rational::try_from(value).ok().map(|x| {
24        let (numer, denom) = x.into_numerator_and_denominator();
25        (
26            BigInt::from_biguint(sign, numer.into()),
27            BigUint::from(denom).into(),
28        )
29    })
30}
31
32#[derive(Copy, Clone, Debug, Eq, PartialEq)]
33pub enum BytesToIntError {
34    InvalidLiteral { base: u32 },
35    InvalidBase,
36    DigitLimit { got: usize, limit: usize },
37}
38
39// https://github.com/python/cpython/blob/4e665351082c50018fb31d80db25b4693057393e/Objects/longobject.c#L2977
40// https://github.com/python/cpython/blob/4e665351082c50018fb31d80db25b4693057393e/Objects/longobject.c#L2884
41pub fn bytes_to_int(
42    buf: &[u8],
43    mut base: u32,
44    digit_limit: usize,
45) -> Result<BigInt, BytesToIntError> {
46    if base != 0 && !(2..=36).contains(&base) {
47        return Err(BytesToIntError::InvalidBase);
48    }
49
50    let mut buf = buf.trim_ascii();
51
52    // split sign
53    let sign = match buf.first() {
54        Some(b'+') => Some(Sign::Plus),
55        Some(b'-') => Some(Sign::Minus),
56        None => return Err(BytesToIntError::InvalidLiteral { base }),
57        _ => None,
58    };
59
60    if sign.is_some() {
61        buf = &buf[1..];
62    }
63
64    let mut error_if_nonzero = false;
65    if base == 0 {
66        match (buf.first(), buf.get(1)) {
67            (Some(v), _) if *v != b'0' => base = 10,
68            (_, Some(b'x' | b'X')) => base = 16,
69            (_, Some(b'o' | b'O')) => base = 8,
70            (_, Some(b'b' | b'B')) => base = 2,
71            (_, _) => {
72                // "old" (C-style) octal literal, now invalid. it might still be zero though
73                base = 10;
74                error_if_nonzero = true;
75            }
76        }
77    }
78
79    if error_if_nonzero {
80        if let [_first, others @ .., last] = buf {
81            let is_zero = *last == b'0' && others.iter().all(|&c| c == b'0' || c == b'_');
82            if !is_zero {
83                return Err(BytesToIntError::InvalidLiteral { base });
84            }
85        }
86        return Ok(BigInt::zero());
87    }
88
89    if buf.first().is_some_and(|&v| v == b'0')
90        && buf.get(1).is_some_and(|&v| {
91            (base == 16 && (v == b'x' || v == b'X'))
92                || (base == 8 && (v == b'o' || v == b'O'))
93                || (base == 2 && (v == b'b' || v == b'B'))
94        })
95    {
96        buf = &buf[2..];
97
98        // One underscore allowed here
99        if buf.first().is_some_and(|&v| v == b'_') {
100            buf = &buf[1..];
101        }
102    }
103
104    // Reject empty strings
105    let mut prev = *buf
106        .first()
107        .ok_or(BytesToIntError::InvalidLiteral { base })?;
108
109    // Leading underscore not allowed
110    if prev == b'_' || !prev.is_ascii_alphanumeric() {
111        return Err(BytesToIntError::InvalidLiteral { base });
112    }
113
114    // Verify all characters are digits and underscores
115    let mut digits = 1;
116    for &cur in buf.iter().skip(1) {
117        if cur == b'_' {
118            // Double underscore not allowed
119            if prev == b'_' {
120                return Err(BytesToIntError::InvalidLiteral { base });
121            }
122        } else if cur.is_ascii_alphanumeric() {
123            digits += 1;
124        } else {
125            return Err(BytesToIntError::InvalidLiteral { base });
126        }
127
128        prev = cur;
129    }
130
131    // Trailing underscore not allowed
132    if prev == b'_' {
133        return Err(BytesToIntError::InvalidLiteral { base });
134    }
135
136    if digit_limit > 0 && !base.is_power_of_two() && digits > digit_limit {
137        return Err(BytesToIntError::DigitLimit {
138            got: digits,
139            limit: digit_limit,
140        });
141    }
142
143    let uint = BigUint::parse_bytes(buf, base).ok_or(BytesToIntError::InvalidLiteral { base })?;
144    Ok(BigInt::from_biguint(sign.unwrap_or(Sign::Plus), uint))
145}
146
147// num-bigint now returns Some(inf) for to_f64() in some cases, so just keep that the same for now
148#[inline(always)]
149pub fn bigint_to_finite_float(int: &BigInt) -> Option<f64> {
150    int.to_f64().filter(|f| f.is_finite())
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    const DIGIT_LIMIT: usize = 4300; // Default of Cpython
158
159    #[test]
160    fn bytes_to_int_valid() {
161        for ((buf, base), expected) in [
162            (("0b101", 2), BigInt::from(5)),
163            (("0x_10", 16), BigInt::from(16)),
164            (("0b", 16), BigInt::from(11)),
165            (("+0b101", 2), BigInt::from(5)),
166            (("0_0_0", 10), BigInt::from(0)),
167            (("000", 0), BigInt::from(0)),
168            (("0_100", 10), BigInt::from(100)),
169        ] {
170            assert_eq!(
171                bytes_to_int(buf.as_bytes(), base, DIGIT_LIMIT),
172                Ok(expected)
173            );
174        }
175    }
176
177    #[test]
178    fn bytes_to_int_invalid_literal() {
179        for ((buf, base), expected) in [
180            (("09_99", 0), BytesToIntError::InvalidLiteral { base: 10 }),
181            (("0_", 0), BytesToIntError::InvalidLiteral { base: 10 }),
182            (("0_", 2), BytesToIntError::InvalidLiteral { base: 2 }),
183        ] {
184            assert_eq!(
185                bytes_to_int(buf.as_bytes(), base, DIGIT_LIMIT),
186                Err(expected)
187            )
188        }
189    }
190
191    #[test]
192    fn bytes_to_int_invalid_base() {
193        for base in [1, 37] {
194            assert_eq!(
195                bytes_to_int("012345".as_bytes(), base, DIGIT_LIMIT),
196                Err(BytesToIntError::InvalidBase)
197            )
198        }
199    }
200
201    #[test]
202    fn bytes_to_int_digit_limit() {
203        assert_eq!(
204            bytes_to_int("012345".as_bytes(), 10, 5),
205            Err(BytesToIntError::DigitLimit { got: 6, limit: 5 })
206        );
207    }
208}