Skip to main content

thruput_u256/
signed.rs

1//! A 256-bit signed integer, two's complement over the same `[u32; 8]`.
2//!
3//! [`I256`] is [`U256`]'s bit pattern read as a signed value, and that is not
4//! just an implementation note: addition, subtraction, multiplication and the
5//! bitwise operations are *the same instructions* on both types, so the signed
6//! half of this crate costs almost nothing beyond the operations where the
7//! sign genuinely changes the answer -- comparison, division, right shift,
8//! and formatting.
9//!
10//! Semantics follow Rust's primitives, which on every point that matters are
11//! also Solidity's:
12//!
13//! * Division truncates toward zero, and the remainder takes the sign of the
14//!   dividend (`-7 / 2 == -3`, `-7 % 2 == -1`).
15//! * `>>` is an arithmetic shift: the sign bit is replicated.
16//! * `MIN.abs()` and `-MIN` overflow. Like the operators on [`U256`], they
17//!   panic when `overflow-checks` is on and wrap when it is off; the
18//!   `checked_` forms report instead.
19//! * `MIN / -1` and `MIN % -1` overflow too, but they **panic
20//!   unconditionally**, in a release contract profile as well. That is not an
21//!   inconsistency: division overflow is the one case the primitives also
22//!   leave ungated, because on real hardware the alternative is a trap rather
23//!   than a wrapped value. `i128::MIN / -1` panics under
24//!   `-C overflow-checks=off` too. Use `wrapping_div`/`checked_div` where an
25//!   abort is not wanted.
26//!
27//! The wire encoding is the two's complement value as 32 big-endian bytes --
28//! the same 32 bytes a `U256` with those bits would produce, and what
29//! Ethereum's `int256` uses.
30//!
31//! # Where the operators differ from Solidity
32//!
33//! The *library functions* match OpenZeppelin exactly, degenerate inputs
34//! included -- `tests/oz_parity.rs` pins that case by case. The *operators*
35//! follow Rust, and on two points Rust and Solidity genuinely disagree. A port
36//! should know which:
37//!
38//! | | Solidity (checked, the default) | Solidity `unchecked` | Rust, and here |
39//! |---|---|---|---|
40//! | `x / 0`, `x % 0` | revert | revert (not disableable) | panic |
41//! | `MIN / -1` | revert | wraps to `MIN` | panic |
42//! | `MIN % -1` | `0` | `0` | **panic** |
43//!
44//! `MIN / -1` agrees with Solidity's default, which is the one that matters:
45//! `unchecked` is opt-in per block in Solidity, whereas `overflow-checks` here
46//! is a whole-build setting nobody wrote at the call site, so treating the two
47//! as equivalent would silently produce `MIN` where the ported original
48//! reverted.
49//!
50//! `MIN % -1` is the real divergence. Solidity's `smod` is a separate opcode
51//! that simply answers 0, and 0 is the mathematically correct remainder;
52//! Rust panics because x86 computes quotient and remainder with one
53//! instruction that traps on both. We follow Rust so that `I256` and the
54//! `i128` beside it in the same expression behave alike -- the rule the rest
55//! of this type's overflow behaviour is built on. Use
56//! [`wrapping_rem`](I256::wrapping_rem), which answers 0, if a port depends
57//! on the Solidity result.
58
59use crate::{LIMBS, TryFromIntError, U256};
60
61/// A 256-bit signed integer.
62///
63/// Limbs are the two's complement representation, least-significant-first, so
64/// the layout and the wire encoding match [`U256`] exactly. See the module
65/// documentation for the semantics.
66///
67#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
68pub struct I256 {
69    pub(crate) limbs: [u32; LIMBS],
70}
71
72impl I256 {
73    /// The additive identity.
74    pub const ZERO: Self = Self::from_limbs([0; LIMBS]);
75
76    /// The multiplicative identity.
77    pub const ONE: Self = Self::from_limbs([1, 0, 0, 0, 0, 0, 0, 0]);
78
79    /// Negative one, whose two's complement is all ones.
80    pub const MINUS_ONE: Self = Self::from_limbs([u32::MAX; LIMBS]);
81
82    /// The largest representable value, `2^255 - 1`.
83    pub const MAX: Self = U256::MAX.wrapping_shr(1).cast_signed();
84
85    /// The smallest representable value, `-2^255`.
86    ///
87    /// Its magnitude is one larger than [`MAX`](Self::MAX)'s, which is why
88    /// negating, absolute value and division by `-1` can all overflow.
89    pub const MIN: Self = U256::ONE.wrapping_shl(255).cast_signed();
90
91    /// The size of this integer in bits, sign bit included.
92    pub const BITS: u32 = 256;
93
94    /// Constructs a value from its two's complement limbs,
95    /// least-significant-first.
96    #[inline]
97    pub const fn from_limbs(limbs: [u32; LIMBS]) -> Self {
98        Self { limbs }
99    }
100
101    /// Returns the two's complement limbs, least-significant-first.
102    #[inline]
103    pub const fn to_limbs(self) -> [u32; LIMBS] {
104        self.limbs
105    }
106
107    /// Constructs a value from an `i128`.
108    ///
109    /// Inherent for the reason [`U256::from`] is: `From` is implemented for
110    /// every signed width, which would leave `I256::from(-1000)` ambiguous,
111    /// and an inherent function shadows the trait in path form and pins the
112    /// literal to `i128`. It is also usable in const position.
113    ///
114    /// The trait impls are untouched, so `.into()` and generic `T: Into<I256>`
115    /// still convert from any width -- including the unsigned ones, which all
116    /// fit.
117    #[inline]
118    pub const fn from(n: i128) -> Self {
119        Self::from_i128(n)
120    }
121
122    /// Constructs a value from an `i128`, under a name no trait shadows.
123    #[inline]
124    pub const fn from_i128(n: i128) -> Self {
125        // Sign-extend: the upper limbs are all ones for a negative value.
126        let fill = if n < 0 { u32::MAX } else { 0 };
127        Self::from_limbs([
128            n as u32,
129            (n >> 32) as u32,
130            (n >> 64) as u32,
131            (n >> 96) as u32,
132            fill,
133            fill,
134            fill,
135            fill,
136        ])
137    }
138
139    /// Constructs a value from an `i32`, without widening at the call site.
140    #[inline]
141    pub const fn from_i32(n: i32) -> Self {
142        let fill = if n < 0 { u32::MAX } else { 0 };
143        Self::from_limbs([n as u32, fill, fill, fill, fill, fill, fill, fill])
144    }
145
146    /// Constructs a value from an `i64`, without widening at the call site.
147    #[inline]
148    pub const fn from_i64(n: i64) -> Self {
149        let fill = if n < 0 { u32::MAX } else { 0 };
150        Self::from_limbs([
151            n as u32,
152            (n >> 32) as u32,
153            fill,
154            fill,
155            fill,
156            fill,
157            fill,
158            fill,
159        ])
160    }
161
162    /// Reinterprets the two's complement bits as a [`U256`].
163    ///
164    /// No arithmetic: `(-1).cast_unsigned()` is [`U256::MAX`]. Named after
165    /// `i128::cast_unsigned`, which does the same thing at a narrower width.
166    #[inline]
167    pub const fn cast_unsigned(self) -> U256 {
168        U256::from_limbs(self.limbs)
169    }
170
171    /// Returns `true` if this is zero.
172    #[inline]
173    pub const fn is_zero(self) -> bool {
174        self.cast_unsigned().is_zero()
175    }
176
177    /// Returns `true` if this is strictly less than zero.
178    #[inline]
179    pub const fn is_negative(self) -> bool {
180        self.limbs[LIMBS - 1] >> 31 == 1
181    }
182
183    /// Returns `true` if this is strictly greater than zero.
184    #[inline]
185    pub const fn is_positive(self) -> bool {
186        !self.is_negative() && !self.is_zero()
187    }
188
189    /// `-1`, `0` or `1`, according to the sign.
190    #[inline]
191    pub const fn signum(self) -> Self {
192        if self.is_zero() {
193            Self::ZERO
194        } else if self.is_negative() {
195            Self::MINUS_ONE
196        } else {
197            Self::ONE
198        }
199    }
200
201    /// The absolute value, as a [`U256`].
202    ///
203    /// Total, unlike [`abs`](Self::abs): `MIN.unsigned_abs()` is `2^255`,
204    /// which a `U256` holds comfortably. `SignedMath.abs`.
205    #[inline]
206    pub const fn unsigned_abs(self) -> U256 {
207        if self.is_negative() {
208            self.cast_unsigned().wrapping_neg()
209        } else {
210            self.cast_unsigned()
211        }
212    }
213
214    /// Returns the bit at `i`, counting from the least significant. Bit 255 is
215    /// the sign bit.
216    ///
217    /// Bits at or above [`BITS`](Self::BITS) read as zero rather than
218    /// panicking, matching [`U256::bit`] -- note that this is *not* a sign
219    /// extension.
220    #[inline]
221    pub const fn bit(self, i: u32) -> bool {
222        self.cast_unsigned().bit(i)
223    }
224
225    /// Comparison usable from a `const fn`.
226    ///
227    /// Flipping the sign bit maps the signed order onto the unsigned one, so
228    /// this is [`U256::const_cmp`] on the flipped values.
229    #[inline]
230    pub const fn const_cmp(self, other: Self) -> core::cmp::Ordering {
231        let flip = U256::ONE.wrapping_shl(255);
232        self.cast_unsigned()
233            .const_bitxor(flip)
234            .const_cmp(other.cast_unsigned().const_bitxor(flip))
235    }
236
237    /// `self == other`, from a `const fn`.
238    #[inline]
239    pub(crate) const fn const_eq(self, other: Self) -> bool {
240        self.cast_unsigned().const_eq(other.cast_unsigned())
241    }
242
243    /// `self < other`, from a `const fn`.
244    #[inline]
245    pub(crate) const fn const_lt(self, other: Self) -> bool {
246        matches!(self.const_cmp(other), core::cmp::Ordering::Less)
247    }
248
249    /// `self > other`, from a `const fn`.
250    #[inline]
251    pub(crate) const fn const_gt(self, other: Self) -> bool {
252        matches!(self.const_cmp(other), core::cmp::Ordering::Greater)
253    }
254
255    /// Returns the big-endian two's complement byte representation.
256    ///
257    /// `#[inline]`, not the `inline(never)` the unsigned pair carries: the
258    /// body is a reinterpretation and a call, so the byte-swap loop this
259    /// reaches is [`U256::to_be_bytes`], which is *already* outlined on rv32
260    /// for exactly that reason. Outlining the wrapper too would add a second
261    /// symbol and a call/return per conversion, not remove one.
262    #[inline]
263    pub const fn to_be_bytes(self) -> [u8; 32] {
264        self.cast_unsigned().to_be_bytes()
265    }
266
267    /// Constructs a value from its big-endian two's complement byte
268    /// representation.
269    ///
270    /// `#[inline]` for the reason [`to_be_bytes`](Self::to_be_bytes) gives.
271    #[inline]
272    pub const fn from_be_bytes(bytes: [u8; 32]) -> Self {
273        U256::from_be_bytes(bytes).cast_signed()
274    }
275}
276
277impl U256 {
278    /// Reinterprets the bits as an [`I256`].
279    ///
280    /// No arithmetic: `U256::MAX.cast_signed()` is `-1`. Named after
281    /// `u128::cast_signed`, which does the same thing at a narrower width.
282    #[inline]
283    pub const fn cast_signed(self) -> I256 {
284        I256::from_limbs(self.limbs)
285    }
286}
287
288// ---------------------------------------------------------------------------
289// Conversions
290// ---------------------------------------------------------------------------
291
292// As in the unsigned half, the bodies call inherent-only names so that their
293// termination does not hang on the inherent `from` shadowing the trait.
294macro_rules! impl_from_signed {
295    ($($t:ty),* $(,)?) => {
296        $(
297            impl From<$t> for I256 {
298                #[inline]
299                fn from(n: $t) -> Self {
300                    Self::from_i32(n as i32)
301                }
302            }
303        )*
304    };
305}
306
307impl_from_signed!(i8, i16, i32);
308
309impl From<i64> for I256 {
310    #[inline]
311    fn from(n: i64) -> Self {
312        Self::from_i64(n)
313    }
314}
315
316impl From<i128> for I256 {
317    #[inline]
318    fn from(n: i128) -> Self {
319        Self::from_i128(n)
320    }
321}
322
323// Every unsigned primitive fits: `u128::MAX` is well below `2^255 - 1`.
324macro_rules! impl_from_unsigned {
325    ($($t:ty),* $(,)?) => {
326        $(
327            impl From<$t> for I256 {
328                #[inline]
329                fn from(n: $t) -> Self {
330                    U256::from_u128(n as u128).cast_signed()
331                }
332            }
333        )*
334    };
335}
336
337impl_from_unsigned!(u8, u16, u32, u64, u128);
338
339impl From<bool> for I256 {
340    #[inline]
341    fn from(b: bool) -> Self {
342        if b { Self::ONE } else { Self::ZERO }
343    }
344}
345
346impl TryFrom<U256> for I256 {
347    type Error = TryFromIntError;
348
349    /// Fails for values above `2^255 - 1`, which would land on a negative
350    /// `I256`. Use [`U256::cast_signed`] to reinterpret those bits instead.
351    #[inline]
352    fn try_from(n: U256) -> Result<Self, Self::Error> {
353        let v = n.cast_signed();
354        if v.is_negative() {
355            Err(TryFromIntError(()))
356        } else {
357            Ok(v)
358        }
359    }
360}
361
362impl TryFrom<I256> for U256 {
363    type Error = TryFromIntError;
364
365    /// Fails for negative values. Use [`I256::cast_unsigned`] to reinterpret
366    /// those bits instead.
367    #[inline]
368    fn try_from(n: I256) -> Result<Self, Self::Error> {
369        if n.is_negative() {
370            Err(TryFromIntError(()))
371        } else {
372            Ok(n.cast_unsigned())
373        }
374    }
375}
376
377macro_rules! impl_try_into_signed {
378    ($($signed:ty => $unsigned:ty),* $(,)?) => {
379        $(
380            impl TryFrom<I256> for $signed {
381                type Error = TryFromIntError;
382
383                #[inline]
384                fn try_from(n: I256) -> Result<Self, Self::Error> {
385                    let mag = <$unsigned>::try_from(n.unsigned_abs())
386                        .map_err(|_| TryFromIntError(()))?;
387                    if n.is_negative() {
388                        // The negative range reaches one further, so the
389                        // bound is on the magnitude, not on the positive
390                        // value it would cast to.
391                        if mag > (1 as $unsigned) << (<$signed>::BITS - 1) {
392                            return Err(TryFromIntError(()));
393                        }
394                        // `mag == 2^(B-1)` casts to `MIN`, whose negation is
395                        // itself -- exactly the answer wanted.
396                        Ok((mag as $signed).wrapping_neg())
397                    } else {
398                        <$signed>::try_from(mag)
399                            .map_err(|_| TryFromIntError(()))
400                    }
401                }
402            }
403        )*
404    };
405}
406
407impl_try_into_signed!(
408    i8 => u8,
409    i16 => u16,
410    i32 => u32,
411    i64 => u64,
412    i128 => u128,
413);
414
415macro_rules! impl_try_into_unsigned {
416    ($($t:ty),* $(,)?) => {
417        $(
418            impl TryFrom<I256> for $t {
419                type Error = TryFromIntError;
420
421                #[inline]
422                fn try_from(n: I256) -> Result<Self, Self::Error> {
423                    if n.is_negative() {
424                        return Err(TryFromIntError(()));
425                    }
426                    <$t>::try_from(n.cast_unsigned())
427                        .map_err(|_| TryFromIntError(()))
428                }
429            }
430        )*
431    };
432}
433
434impl_try_into_unsigned!(u8, u16, u32, u64, u128);
435
436impl PartialOrd for I256 {
437    #[inline]
438    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
439        Some(self.cmp(other))
440    }
441}
442
443impl Ord for I256 {
444    #[inline]
445    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
446        self.const_cmp(*other)
447    }
448}