Skip to content

Commit bb4aec7

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

2 files changed

Lines changed: 208 additions & 207 deletions

File tree

src/utils/FixedPointMathLib.sol

Lines changed: 84 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ 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.
209+
// When the true result is less than 1 wei we return zero.
210210
// This happens when `x <= (log(1e-18) * 1e18) ~ -4.15e19`.
211211
if (x <= -41446531673892822313) return r;
212212

@@ -220,53 +220,34 @@ library FixedPointMathLib {
220220
}
221221
}
222222

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.
223+
// Convert `x` from `10**18` fixed point to `2**96` fixed point.
226224
x = (x << 78) / 5 ** 18;
227225

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).
226+
// Reduce to `x' in (-½ ln 2, ½ ln 2) * 2**96` with `exp(x) = 2**k * exp(x')`.
227+
// `k` is in the range `[-60, 195]`.
231228
int256 k = ((x << 96) / 54916777467707473351141471128 + 2 ** 95) >> 96;
232229
x = x - k * 54916777467707473351141471128;
233230

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;
231+
// `exp(x') = (E + x' * O) / (E - x' * O)`, a (5, 5)-term symmetric
232+
// rational with `E`, `O` polynomials in `x'^2`.
233+
int256 u = (x * x) >> 96;
234+
int256 e = (((u + 8876005618932925505308977557156) * u) >> 96)
235+
+ 79886213883764523772906264239809;
236+
int256 o = ((2639738311906822815584886674 * u) >> 96) + 1109410564309688178690540877381;
237+
o = ((o * u) >> 96) + 39943106941882261691307222498689;
238+
int256 t = (x * o) >> 96;
251239

252240
/// @solidity memory-safe-assembly
253241
assembly {
254242
// 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)
243+
// The denominator is positive on the whole reduced domain.
244+
r := sdiv(shl(96, add(e, t)), sub(e, t))
258245
}
259246

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.
247+
// Multiply by `2**k * 1e18 / 2**96`. `r < 1.5 * 2**96`, so the
248+
// product cannot overflow, and the shift amount is never negative.
268249
r = int256(
269-
(uint256(r) * 3822833074963236453042738258902158003155416615667) >> uint256(195 - k)
250+
(uint256(r) * 633825300114114700748351602688000000000000000000) >> uint256(195 - k)
270251
);
271252
}
272253
}
@@ -277,11 +258,6 @@ library FixedPointMathLib {
277258
function lnWad(int256 x) internal pure returns (int256 r) {
278259
/// @solidity memory-safe-assembly
279260
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-
285261
// Compute `k = log2(x) - 96`, `r = 159 - k = 255 - log2(x) = 255 ^ log2(x)`.
286262
r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
287263
r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
@@ -301,47 +277,44 @@ library FixedPointMathLib {
301277
// ln(2^k * x) = k * ln(2) + ln(x)
302278
x := shr(159, shl(r, x))
303279

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`.
280+
// `s = (x - sqrt(2)) * 2**96 / (x + sqrt(2))`, so that
281+
// `ln(x) = ln(2)/2 + 2 * atanh(s)`.
282+
let s :=
283+
sdiv(
284+
shl(96, sub(x, 112045541949572279837463876455)),
285+
add(x, 112045541949572279837463876455)
286+
)
287+
288+
// `2 * atanh(s) = s * A(w) / B(w)`, a (3, 3)-term odd rational in `w = s^2`.
289+
let w := sar(96, mul(s, s))
290+
let a :=
291+
add(
292+
sar(96, mul(sub(w, 1813347344949966953757847210329), w)),
293+
5824670411451500986303020460168
294+
)
295+
a := sub(sar(96, mul(a, w)), 4518264490991587979207438354337)
296+
let b :=
297+
sub(
298+
sar(96, mul(188151507788160136135094921663, w)),
299+
1676640319226537252003611223372
300+
)
301+
b := add(sar(96, mul(b, w)), 3665379287557676720634158507137)
302+
b := sub(sar(96, mul(b, w)), 2259132245495793985525851698055)
303+
304+
// `B` is bounded away from zero on the whole domain.
305+
let p := sdiv(mul(s, a), b)
306+
307+
// Add `(2k + 1) * ln(2)/2` and `ln(2**96 / 10**18)`, then convert to `WAD`,
308+
// all in `5**18 * 2**192` basis.
309+
p := mul(302231454903657293676544000000000000000000, p)
310+
p := add(
311+
mul(
312+
8298788776342807110743642979096973734596910279609939088954046749604186,
313+
sub(319, shl(1, r))
314+
),
315+
p
316+
)
317+
p := add(600920179829731861736750627322249724520361163382248881493645412721105578, p)
345318
r := sar(174, p)
346319
}
347320
}
@@ -428,12 +401,35 @@ library FixedPointMathLib {
428401
int256 t = w | 1;
429402
/// @solidity memory-safe-assembly
430403
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))
404+
x := sdiv(add(mul(x, wad), shr(1, t)), t)
405+
// Inline the `lnWad` core at `2**96` precision, so that the final
406+
// step rounds to nearest regardless of `lnWad`'s wad rounding.
407+
let v := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
408+
v := or(v, shl(6, lt(0xffffffffffffffff, shr(v, x))))
409+
v := or(v, shl(5, lt(0xffffffff, shr(v, x))))
410+
v := or(v, shl(4, lt(0xffff, shr(v, x))))
411+
v := or(v, shl(3, lt(0xff, shr(v, x))))
412+
// forgefmt: disable-next-item
413+
v := xor(v, byte(and(0x1f, shr(shr(v, x), 0x8421084210842108cc6318c6db6d54be)),
414+
0xf8f9f9faf9fdfafbf9fdfcfdfafbfcfef9fafdfafcfcfbfefafafcfbffffffff))
415+
x := shr(159, shl(v, x))
416+
let s := sdiv(shl(96, sub(x, 112045541949572279837463876455)),
417+
add(x, 112045541949572279837463876455))
418+
let z := sar(96, mul(s, s))
419+
let a := add(sar(96, mul(sub(z, 1813347344949966953757847210329), z)),
420+
5824670411451500986303020460168)
421+
a := sub(sar(96, mul(a, z)), 4518264490991587979207438354337)
422+
let b := sub(sar(96, mul(188151507788160136135094921663, z)),
423+
1676640319226537252003611223372)
424+
b := add(sar(96, mul(b, z)), 3665379287557676720634158507137)
425+
b := sub(sar(96, mul(b, z)), 2259132245495793985525851698055)
426+
// `l = ln(x' / 1e18) * 2**96`.
427+
let l := add(sdiv(mul(s, a), b),
428+
add(mul(27458388733853736675570735564, sub(319, shl(1, v))),
429+
1988278089788132588087242333381))
430+
// `w = t * (2**96 + l) * 1e18 / (2**96 * (1e18 + t))`, rounded to nearest.
431+
let d := mul(shl(96, 1), add(wad, t))
432+
w := sdiv(add(mul(mul(t, add(shl(96, 1), l)), wad), shr(1, d)), d)
437433
}
438434
}
439435
}

0 commit comments

Comments
 (0)