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}