Skip to main content

flux_middle/
big_int.rs

1use std::{cmp::Ordering, fmt};
2
3use rustc_macros::{Decodable, Encodable};
4
5/// A signed integer in the range [-2^128, 2^128], represented by a `u128` and an explicit sign.
6///
7/// In the logic, we work mathematical integers so we could represent them with arbitrary precision
8/// integers. We instead take the simpler approach of using a fixed size representation that allows
9/// us to store any Rust literal, i.e., we can represent both `i128::MIN` and `u128::MAX`. This works
10/// because we never do arithmetic. We can change the representation in the future (and use arbitrary
11/// precision integers) if this ever becomes a problem, e.g., if we want to do (precise) arithmetic
12/// during constant folding.
13#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash, Encodable, Decodable)]
14pub struct BigInt {
15    sign: Sign,
16    val: u128,
17}
18
19impl PartialOrd for BigInt {
20    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
21        Some(self.cmp(other))
22    }
23}
24
25impl Ord for BigInt {
26    fn cmp(&self, other: &Self) -> Ordering {
27        match (self.sign, other.sign) {
28            (Sign::Negative, Sign::NonNegative) => Ordering::Less,
29            (Sign::NonNegative, Sign::Negative) => Ordering::Greater,
30            (Sign::NonNegative, Sign::NonNegative) => self.val.cmp(&other.val),
31            (Sign::Negative, Sign::Negative) => other.val.cmp(&self.val),
32        }
33    }
34}
35
36/// This are in order so negative is less than non-negative.
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Encodable, Decodable, PartialOrd, Ord)]
38enum Sign {
39    Negative,
40    NonNegative,
41}
42
43impl BigInt {
44    pub const ZERO: BigInt = BigInt { sign: Sign::NonNegative, val: 0 };
45    pub const ONE: BigInt = BigInt { sign: Sign::NonNegative, val: 1 };
46
47    pub fn is_negative(&self) -> bool {
48        matches!(self.sign, Sign::Negative)
49    }
50
51    pub fn abs(&self) -> u128 {
52        self.val
53    }
54
55    /// Given the bit width of a signed integer type, produces the minimum integer for
56    /// that type, i.e., -2^(bit_width - 1).
57    pub fn int_min(bit_width: u32) -> BigInt {
58        BigInt { sign: Sign::Negative, val: 1u128 << (bit_width - 1) }
59    }
60
61    /// Given the bit width of a signed integer type, produces the maximum integer for
62    /// that type, i.e., 2^(bit_width - 1) - 1.
63    pub fn int_max(bit_width: u32) -> BigInt {
64        (i128::MAX >> (128 - bit_width)).into()
65    }
66
67    /// Given the bit width of an unsigned integer type, produces the maximum
68    /// unsigned integer for that type, i.e., 2^bit_width - 1.
69    pub fn uint_max(bit_width: u32) -> BigInt {
70        (u128::MAX >> (128 - bit_width)).into()
71    }
72
73    pub fn neg(&self) -> Self {
74        if self.val == 0 {
75            Self::ZERO // Avoid negative zero
76        } else {
77            Self {
78                sign: match self.sign {
79                    Sign::Negative => Sign::NonNegative,
80                    Sign::NonNegative => Sign::Negative,
81                },
82                val: self.val,
83            }
84        }
85    }
86
87    pub fn checked_add(&self, other: &Self) -> Option<Self> {
88        if self.sign == other.sign {
89            Some(Self { sign: self.sign, val: self.val.checked_add(other.val)? })
90        } else if self.val >= other.val {
91            let val = self.val - other.val;
92            if val == 0 { Some(Self::ZERO) } else { Some(Self { sign: self.sign, val }) }
93        } else {
94            Some(Self { sign: other.sign, val: other.val - self.val })
95        }
96    }
97
98    pub fn checked_sub(&self, other: &Self) -> Option<Self> {
99        self.checked_add(&other.neg())
100    }
101
102    pub fn checked_mul(&self, other: &Self) -> Option<Self> {
103        let val = self.val.checked_mul(other.val)?;
104        if val == 0 {
105            Some(Self::ZERO)
106        } else {
107            let sign = if self.sign == other.sign { Sign::NonNegative } else { Sign::Negative };
108            Some(Self { sign, val })
109        }
110    }
111
112    pub fn checked_div(&self, other: &Self) -> Option<Self> {
113        if other.val == 0 {
114            return None;
115        } // Divide by zero
116        let val = self.val / other.val;
117        if val == 0 {
118            Some(Self::ZERO)
119        } else {
120            let sign = if self.sign == other.sign { Sign::NonNegative } else { Sign::Negative };
121            Some(Self { sign, val })
122        }
123    }
124
125    pub fn checked_rem(&self, other: &Self) -> Option<Self> {
126        if other.val == 0 {
127            return None;
128        } // Divide by zero
129        let val = self.val % other.val;
130        if val == 0 {
131            Some(Self::ZERO)
132        } else {
133            // In Rust modulo, the remainder takes the sign of the dividend (self)
134            Some(Self { sign: self.sign, val })
135        }
136    }
137}
138
139impl From<usize> for BigInt {
140    fn from(val: usize) -> Self {
141        BigInt { sign: Sign::NonNegative, val: val as u128 }
142    }
143}
144
145impl From<u128> for BigInt {
146    fn from(val: u128) -> Self {
147        BigInt { sign: Sign::NonNegative, val }
148    }
149}
150
151impl From<i128> for BigInt {
152    fn from(val: i128) -> Self {
153        let sign = if val < 0 { Sign::Negative } else { Sign::NonNegative };
154        BigInt { sign, val: val.unsigned_abs() }
155    }
156}
157
158impl From<i32> for BigInt {
159    fn from(val: i32) -> Self {
160        // TODO(nilehmann) use Flux to prove this doesn't overflow
161        if val < 0 {
162            BigInt { sign: Sign::Negative, val: -(val as i64) as u128 }
163        } else {
164            BigInt { sign: Sign::NonNegative, val: val as u128 }
165        }
166    }
167}
168
169impl From<u32> for BigInt {
170    fn from(val: u32) -> Self {
171        BigInt { sign: Sign::NonNegative, val: val as u128 }
172    }
173}
174
175impl From<u64> for BigInt {
176    fn from(val: u64) -> Self {
177        BigInt { sign: Sign::NonNegative, val: val as u128 }
178    }
179}
180
181impl fmt::Display for BigInt {
182    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183        match self.sign {
184            Sign::NonNegative => write!(f, "{}", self.val),
185            Sign::Negative => write!(f, "-{}", self.val),
186        }
187    }
188}