Skip to main content

thruput_u256/
signed_math.rs

1//! Signed math utilities: OpenZeppelin's `SignedMath.sol`, in Rust, plus the
2//! signed half of the `mulDiv` family.
3//!
4//! `SignedMath.sol` is five functions. Four have direct counterparts here
5//! ([`min`](I256::min), [`max`](I256::max), [`average`](I256::average),
6//! and `abs` -- which is [`unsigned_abs`](I256::unsigned_abs), already total
7//! and already returning a [`U256`] exactly as Solidity's does). The fifth,
8//! `ternary`, is a branchless-select trick for the EVM's gas model; in Rust it
9//! is `if`.
10//!
11//! Solidity has no signed `mulDiv`, so [`mul_div`](I256::mul_div) here is the
12//! obvious composition rather than a port: magnitudes go through
13//! [`U256::mul_div`], and the sign comes back afterwards. It truncates toward
14//! zero, the same rule `/` uses, and the rounding variant takes the same
15//! [`Rounding`] the unsigned side does -- where the two signed directions stop
16//! coinciding, and [`Rounding::rounds_away_from_zero`] is what decides.
17
18use crate::{I256, Rounding, U256};
19
20impl I256 {
21    // -----------------------------------------------------------------------
22    // Comparison and averaging
23    // -----------------------------------------------------------------------
24
25    /// The smaller of two values. `SignedMath.min`.
26    ///
27    /// Shadows [`Ord::min`] in method position, which resolves to the same
28    /// answer; this one is a `const fn`.
29    #[inline]
30    pub const fn min(self, other: Self) -> Self {
31        if self.const_lt(other) { self } else { other }
32    }
33
34    /// The larger of two values. `SignedMath.max`.
35    ///
36    /// Shadows [`Ord::max`] in method position, which resolves to the same
37    /// answer; this one is a `const fn`.
38    #[inline]
39    pub const fn max(self, other: Self) -> Self {
40        if self.const_gt(other) { self } else { other }
41    }
42
43    /// Clamps to the inclusive range `min..=max`.
44    ///
45    /// # Panics
46    ///
47    /// If `min > max`, matching [`Ord::clamp`].
48    #[inline]
49    pub const fn clamp(self, min: Self, max: Self) -> Self {
50        if min.const_gt(max) {
51            panic!("min must be less than or equal to max");
52        }
53        self.max(min).min(max)
54    }
55
56    /// The average of two values, **rounded toward zero**, without an
57    /// intermediate that overflows. `SignedMath.average`.
58    ///
59    /// `(a & b) + ((a ^ b) >> 1)` is the floor average -- the shift is
60    /// arithmetic, so it rounds toward negative infinity -- and the
61    /// correction below pulls a negative, inexact result back up by one.
62    /// Note that this differs from [`U256::average`] only in that
63    /// correction, because for a value that cannot be negative the two
64    /// directions coincide.
65    #[inline]
66    pub const fn average(self, other: Self) -> Self {
67        let xor = self.const_bitxor(other);
68        let floor = self.const_bitand(other).wrapping_add(xor.wrapping_shr(1));
69        // The sum was odd (so the halving lost a half) and the result is
70        // below zero (so "toward zero" means up).
71        if floor.is_negative() && xor.bit(0) {
72            floor.wrapping_add(Self::ONE)
73        } else {
74            floor
75        }
76    }
77
78    // -----------------------------------------------------------------------
79    // Full-precision multiply-divide
80    // -----------------------------------------------------------------------
81
82    /// `x * y / d` computed on the exact 512-bit product, truncating toward
83    /// zero. Returns `None` if `d` is zero or the quotient is out of range.
84    ///
85    /// The signed counterpart of [`U256::mul_div`]; see the module docs for
86    /// why it is a composition rather than a port.
87    #[inline]
88    pub const fn checked_mul_div(x: Self, y: Self, d: Self) -> Option<Self> {
89        match Self::mul_div_parts(x, y, d) {
90            Some((q, _, _)) => Some(q),
91            None => None,
92        }
93    }
94
95    /// `x * y / d` computed on the exact 512-bit product, truncating toward
96    /// zero.
97    ///
98    /// # Panics
99    ///
100    /// If `d` is zero, or if the quotient is out of range. Both panics are
101    /// unconditional, as on the unsigned side.
102    #[inline]
103    pub const fn mul_div(x: Self, y: Self, d: Self) -> Self {
104        match Self::checked_mul_div(x, y, d) {
105            Some(v) => v,
106            None => {
107                if d.is_zero() {
108                    panic!("attempt to divide by zero");
109                }
110                panic!("mul_div quotient out of range for I256");
111            }
112        }
113    }
114
115    /// `x * y / d` computed on the exact 512-bit product, following
116    /// `rounding`. Returns `None` if `d` is zero or the result is out of
117    /// range.
118    #[inline]
119    pub const fn checked_mul_div_rounding(
120        x: Self,
121        y: Self,
122        d: Self,
123        rounding: Rounding,
124    ) -> Option<Self> {
125        let (q, inexact, negative) = match Self::mul_div_parts(x, y, d) {
126            Some(v) => v,
127            None => return None,
128        };
129        if !inexact || !rounding.rounds_away_from_zero(negative) {
130            return Some(q);
131        }
132        if negative {
133            q.checked_sub(Self::ONE)
134        } else {
135            q.checked_add(Self::ONE)
136        }
137    }
138
139    /// `x * y / d` computed on the exact 512-bit product, following
140    /// `rounding`.
141    ///
142    /// # Panics
143    ///
144    /// As [`mul_div`](Self::mul_div).
145    #[inline]
146    pub const fn mul_div_rounding(
147        x: Self,
148        y: Self,
149        d: Self,
150        rounding: Rounding,
151    ) -> Self {
152        match Self::checked_mul_div_rounding(x, y, d, rounding) {
153            Some(v) => v,
154            None => {
155                if d.is_zero() {
156                    panic!("attempt to divide by zero");
157                }
158                panic!("mul_div quotient out of range for I256");
159            }
160        }
161    }
162
163    /// The truncated quotient of `x * y / d`, whether it was exact, and the
164    /// sign it carries -- or `None` when `d` is zero or the quotient is out of
165    /// range.
166    ///
167    /// The sign is returned rather than re-derived because the quotient may be
168    /// zero, which loses it, and the rounding direction of an inexact
169    /// zero-magnitude result still depends on which side of zero it came from.
170    #[inline]
171    const fn mul_div_parts(
172        x: Self,
173        y: Self,
174        d: Self,
175    ) -> Option<(Self, bool, bool)> {
176        let negative = (x.is_negative() != y.is_negative()) != d.is_negative();
177
178        let (mag, rem) = match U256::mul_div_parts(
179            x.unsigned_abs(),
180            y.unsigned_abs(),
181            d.unsigned_abs(),
182        ) {
183            Some(v) => v,
184            None => return None,
185        };
186
187        // The negative range reaches one further, so the bound is on the
188        // magnitude rather than on the value it would become.
189        let limit = if negative {
190            U256::ONE.wrapping_shl(255)
191        } else {
192            Self::MAX.cast_unsigned()
193        };
194        if mag.const_gt(limit) {
195            return None;
196        }
197
198        let q = if negative { mag.wrapping_neg() } else { mag };
199        Some((q.cast_signed(), !rem.is_zero(), negative))
200    }
201}