Skip to content

Commit 46fcb70

Browse files
committed
Cheaper expWad and lnWad via symmetric rationals
1 parent c251232 commit 46fcb70

2 files changed

Lines changed: 221 additions & 215 deletions

File tree

src/utils/FixedPointMathLib.sol

Lines changed: 97 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -206,67 +206,53 @@ library FixedPointMathLib {
206206
/// Note: This function is an approximation. Monotonically increasing.
207207
function expWad(int256 x) internal pure returns (int256 r) {
208208
unchecked {
209-
// When the result is less than 0.5 we return zero.
210-
// This happens when `x <= (log(1e-18) * 1e18) ~ -4.15e19`.
211-
if (x <= -41446531673892822313) return r;
209+
// Accept `-41446531673892822313 < x < 135305999368893231589` with a
210+
// single unsigned comparison; sort out the two edges on the cold path.
211+
if (uint256(x) + 41446531673892822312 >= 176752531042786053901) {
212+
// When the true result is less than 1 wei we return zero.
213+
// This happens when `x <= (log(1e-18) * 1e18) ~ -4.15e19`.
214+
if (x <= -41446531673892822313) return r;
212215

213-
/// @solidity memory-safe-assembly
214-
assembly {
215-
// When the result is greater than `(2**255 - 1) / 1e18` we can not represent it as
216-
// an int. This happens when `x >= floor(log((2**255 - 1) / 1e18) * 1e18) ≈ 135`.
217-
if iszero(slt(x, 135305999368893231589)) {
216+
/// @solidity memory-safe-assembly
217+
assembly {
218+
// When the result is greater than `(2**255 - 1) / 1e18` we can not
219+
// represent it as an int. This happens when
220+
// `x >= floor(log((2**255 - 1) / 1e18) * 1e18) ≈ 135`.
218221
mstore(0x00, 0xa37bfec9) // `ExpOverflow()`.
219222
revert(0x1c, 0x04)
220223
}
221224
}
222225

223-
// `x` is now in the range `(-42, 136) * 1e18`. Convert to `(-42, 136) * 2**96`
224-
// for more intermediate precision and a binary basis. This base conversion
225-
// is a multiplication by 1e18 / 2**96 = 5**18 / 2**78.
226+
// Convert `x` from `10**18` fixed point to `2**96` fixed point.
226227
x = (x << 78) / 5 ** 18;
227228

228-
// Reduce range of x to (-½ ln 2, ½ ln 2) * 2**96 by factoring out powers
229-
// of two such that exp(x) = exp(x') * 2**k, where k is an integer.
230-
// Solving this gives k = round(x / log(2)) and x' = x - k * log(2).
231-
int256 k = ((x << 96) / 54916777467707473351141471128 + 2 ** 95) >> 96;
229+
// Reduce to `x' in (-½ ln 2, ½ ln 2) * 2**96` with `exp(x) = 2**k * exp(x')`.
230+
// `6196328019 = round(2**128 / (ln 2 * 2**96))`; `k` is in the range `[-60, 195]`.
231+
int256 k = (x * 6196328019 + 2 ** 127) >> 128;
232232
x = x - k * 54916777467707473351141471128;
233233

234-
// `k` is in the range `[-61, 195]`.
235-
236-
// Evaluate using a (6, 7)-term rational approximation.
237-
// `p` is made monic, we'll multiply by a scale factor later.
238-
int256 y = x + 1346386616545796478920950773328;
239-
y = ((y * x) >> 96) + 57155421227552351082224309758442;
240-
int256 p = y + x - 94201549194550492254356042504812;
241-
p = ((p * y) >> 96) + 28719021644029726153956944680412240;
242-
p = p * x + (4385272521454847904659076985693276 << 96);
243-
244-
// We leave `p` in `2**192` basis so we don't need to scale it back up for the division.
245-
int256 q = x - 2855989394907223263936484059900;
246-
q = ((q * x) >> 96) + 50020603652535783019961831881945;
247-
q = ((q * x) >> 96) - 533845033583426703283633433725380;
248-
q = ((q * x) >> 96) + 3604857256930695427073651918091429;
249-
q = ((q * x) >> 96) - 14423608567350463180887372962807573;
250-
q = ((q * x) >> 96) + 26449188498355588339934803723976023;
234+
// `exp(x') = (E + x' * O) / (E - x' * O)`, a (5, 5)-term symmetric
235+
// rational with `E`, `O` polynomials in `x'^2`. `E` is monic, so its
236+
// single Horner stage needs no `>> 96`: with the constant term
237+
// pre-shifted, `e` and `t` are in `2**192` basis.
238+
int256 u = (x * x) >> 96;
239+
int256 e = (u + 8876005618932925505308977557156) * u
240+
+ (79886213883764523772906264239809 << 96);
241+
int256 o = ((2639738311906822815584886674 * u) >> 96) + 1109410564309688178690540877381;
242+
o = ((o * u) >> 96) + 39943106941882261691307222498689;
243+
int256 t = x * o;
251244

252245
/// @solidity memory-safe-assembly
253246
assembly {
254247
// Div in assembly because solidity adds a zero check despite the unchecked.
255-
// The q polynomial won't have zeros in the domain as all its roots are complex.
256-
// No scaling is necessary because p is already `2**96` too large.
257-
r := sdiv(p, q)
248+
// The denominator is positive on the whole reduced domain.
249+
r := sdiv(add(e, t), sar(96, sub(e, t)))
258250
}
259251

260-
// r should be in the range `(0.09, 0.25) * 2**96`.
261-
262-
// We now need to multiply r by:
263-
// - The scale factor `s ≈ 6.031367120`.
264-
// - The `2**k` factor from the range reduction.
265-
// - The `1e18 / 2**96` factor for base conversion.
266-
// We do this all at once, with an intermediate result in `2**213`
267-
// basis, so the final right shift is always by a positive amount.
252+
// Multiply by `2**k * 1e18 / 2**96`. `r < 1.5 * 2**96`, so the
253+
// product cannot overflow, and the shift amount is never negative.
268254
r = int256(
269-
(uint256(r) * 3822833074963236453042738258902158003155416615667) >> uint256(195 - k)
255+
(uint256(r) * 633825300114114700748351602688000000000000000000) >> uint256(195 - k)
270256
);
271257
}
272258
}
@@ -277,11 +263,6 @@ library FixedPointMathLib {
277263
function lnWad(int256 x) internal pure returns (int256 r) {
278264
/// @solidity memory-safe-assembly
279265
assembly {
280-
// We want to convert `x` from `10**18` fixed point to `2**96` fixed point.
281-
// We do this by multiplying by `2**96 / 10**18`. But since
282-
// `ln(x * C) = ln(x) + ln(C)`, we can simply do nothing here
283-
// and add `ln(2**96 / 10**18)` at the end.
284-
285266
// Compute `k = log2(x) - 96`, `r = 159 - k = 255 - log2(x) = 255 ^ log2(x)`.
286267
r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
287268
r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
@@ -301,47 +282,44 @@ library FixedPointMathLib {
301282
// ln(2^k * x) = k * ln(2) + ln(x)
302283
x := shr(159, shl(r, x))
303284

304-
// Evaluate using a (8, 8)-term rational approximation.
305-
// `p` is made monic, we will multiply by a scale factor later.
306-
// forgefmt: disable-next-item
307-
let p := sub( // This heavily nested expression is to avoid stack-too-deep for via-ir.
308-
sar(96, mul(add(43456485725739037958740375743393,
309-
sar(96, mul(add(24828157081833163892658089445524,
310-
sar(96, mul(add(3273285459638523848632254066296,
311-
x), x))), x))), x)), 11111509109440967052023855526967)
312-
p := sub(sar(96, mul(p, x)), 45023709667254063763336534515857)
313-
p := sub(sar(96, mul(p, x)), 14706773417378608786704636184526)
314-
p := sub(mul(p, x), shl(96, 795164235651350426258249787498))
315-
// We leave `p` in `2**192` basis so we don't need to scale it back up for the division.
316-
317-
// `q` is monic by convention.
318-
let q := add(5573035233440673466300451813936, x)
319-
q := add(71694874799317883764090561454958, sar(96, mul(x, q)))
320-
q := add(283447036172924575727196451306956, sar(96, mul(x, q)))
321-
q := add(401686690394027663651624208769553, sar(96, mul(x, q)))
322-
q := add(204048457590392012362485061816622, sar(96, mul(x, q)))
323-
q := add(31853899698501571402653359427138, sar(96, mul(x, q)))
324-
q := add(909429971244387300277376558375, sar(96, mul(x, q)))
325-
326-
// `p / q` is in the range `(0, 0.125) * 2**96`.
327-
328-
// Finalization, we need to:
329-
// - Multiply by the scale factor `s = 5.549…`.
330-
// - Add `ln(2**96 / 10**18)`.
331-
// - Add `k * ln(2)`.
332-
// - Multiply by `10**18 / 2**96 = 5**18 >> 78`.
333-
334-
// The q polynomial is known not to have zeros in the domain.
335-
// No scaling required because p is already `2**96` too large.
336-
p := sdiv(p, q)
337-
// Multiply by the scaling factor: `s * 5**18 * 2**96`, base is now `5**18 * 2**192`.
338-
p := mul(1677202110996718588342820967067443963516166, p)
339-
// Add `ln(2) * k * 5**18 * 2**192`.
340-
// forgefmt: disable-next-item
341-
p := add(mul(16597577552685614221487285958193947469193820559219878177908093499208371, sub(159, r)), p)
342-
// Add `ln(2**96 / 10**18) * 5**18 * 2**192`.
343-
p := add(600920179829731861736702779321621459595472258049074101567377883020018308, p)
344-
// Base conversion: mul `2**18 / 2**192`.
285+
// `s = (x - sqrt(2)) * 2**96 / (x + sqrt(2))`, so that
286+
// `ln(x) = ln(2)/2 + 2 * atanh(s)`.
287+
let s :=
288+
sdiv(
289+
shl(96, sub(x, 112045541949572279837463876455)),
290+
add(x, 112045541949572279837463876455)
291+
)
292+
293+
// `2 * atanh(s) = s * A(w) / B(w)`, a (3, 3)-term odd rational in `w = s^2`.
294+
let w := sar(96, mul(s, s))
295+
let a :=
296+
add(
297+
sar(96, mul(sub(w, 1813347344949966953757847210329), w)),
298+
5824670411451500986303020460168
299+
)
300+
a := sub(sar(96, mul(a, w)), 4518264490991587979207438354337)
301+
let b :=
302+
sub(
303+
sar(96, mul(188151507788160136135094921663, w)),
304+
1676640319226537252003611223372
305+
)
306+
b := add(sar(96, mul(b, w)), 3665379287557676720634158507137)
307+
b := sub(sar(96, mul(b, w)), 2259132245495793985525851698055)
308+
309+
// `B` is bounded away from zero on the whole domain.
310+
let p := sdiv(mul(s, a), b)
311+
312+
// Add `(2k + 1) * ln(2)/2` and `ln(2**96 / 10**18)`, then convert to `WAD`,
313+
// all in `5**18 * 2**192` basis.
314+
p := mul(302231454903657293676544000000000000000000, p)
315+
p := add(
316+
mul(
317+
8298788776342807110743642979096973734596910279609939088954046749604186,
318+
sub(319, shl(1, r))
319+
),
320+
p
321+
)
322+
p := add(600920179829731861736750627322249724520361163382248881493645412721105578, p)
345323
r := sar(174, p)
346324
}
347325
}
@@ -428,12 +406,35 @@ library FixedPointMathLib {
428406
int256 t = w | 1;
429407
/// @solidity memory-safe-assembly
430408
assembly {
431-
x := sdiv(mul(x, wad), t)
432-
}
433-
x = (t * (wad + lnWad(x)));
434-
/// @solidity memory-safe-assembly
435-
assembly {
436-
w := sdiv(x, add(wad, t))
409+
x := sdiv(add(mul(x, wad), shr(1, t)), t)
410+
// Inline the `lnWad` core at `2**96` precision, so that the final
411+
// step rounds to nearest regardless of `lnWad`'s wad rounding.
412+
let v := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
413+
v := or(v, shl(6, lt(0xffffffffffffffff, shr(v, x))))
414+
v := or(v, shl(5, lt(0xffffffff, shr(v, x))))
415+
v := or(v, shl(4, lt(0xffff, shr(v, x))))
416+
v := or(v, shl(3, lt(0xff, shr(v, x))))
417+
// forgefmt: disable-next-item
418+
v := xor(v, byte(and(0x1f, shr(shr(v, x), 0x8421084210842108cc6318c6db6d54be)),
419+
0xf8f9f9faf9fdfafbf9fdfcfdfafbfcfef9fafdfafcfcfbfefafafcfbffffffff))
420+
x := shr(159, shl(v, x))
421+
let s := sdiv(shl(96, sub(x, 112045541949572279837463876455)),
422+
add(x, 112045541949572279837463876455))
423+
let z := sar(96, mul(s, s))
424+
let a := add(sar(96, mul(sub(z, 1813347344949966953757847210329), z)),
425+
5824670411451500986303020460168)
426+
a := sub(sar(96, mul(a, z)), 4518264490991587979207438354337)
427+
let b := sub(sar(96, mul(188151507788160136135094921663, z)),
428+
1676640319226537252003611223372)
429+
b := add(sar(96, mul(b, z)), 3665379287557676720634158507137)
430+
b := sub(sar(96, mul(b, z)), 2259132245495793985525851698055)
431+
// `l = ln(x' / 1e18) * 2**96`.
432+
let l := add(sdiv(mul(s, a), b),
433+
add(mul(27458388733853736675570735564, sub(319, shl(1, v))),
434+
1988278089788132588087242333381))
435+
// `w = t * (2**96 + l) * 1e18 / (2**96 * (1e18 + t))`, rounded to nearest.
436+
let d := mul(shl(96, 1), add(wad, t))
437+
w := sdiv(add(mul(mul(t, add(shl(96, 1), l)), wad), shr(1, d)), d)
437438
}
438439
}
439440
}

0 commit comments

Comments
 (0)