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}