Binary Fractions
I currently know of only one solution, authored by Mikhail Vladimirov. It works by computing the input as a binary fraction.
Code
ABDKMath64x64.sol has an exp_2 function, which looks like this:
function exp_2(int128 x) internal pure returns (int128) {
unchecked {
require(x < 0x400000000000000000); // Overflow
if (x < -0x400000000000000000) return 0; // Underflow
uint256 result = 0x80000000000000000000000000000000;
// REMOVED: Binary fraction logic, explained below...
result >>= uint256(int256(63 - (x >> 64)));
require(result <= uint256(int256(MAX_64x64)));
return int128(int256(result));
}
}
For brevity, I skipped a big chunk of the function body.
Explanation
Any fractional number can be written as the sum of the integer part and the fractional part:
x = n + f, 0 <= f < 1
Then, using x as an exponent:
y = 2^x = 2^(n + f) = 2^n * 2^f;
Observing that we can write f as a binary fraction:
f = f1 * 2^-1 + f2 * 2^-2 + ...
Where f_i can be either 0 or 1. Then:
y = 2^n * (2^(f1*2^-1) * 2^(f2*2^-2) ...)
Note that:
for f_i = 0 => 2^(f_i * 2^-i) = 1
for f_i = 1 => 2^(f_i * 2^-i) = root(2, 2^i)
So we pre-compute these root(2, 2^-i) magic factors and use them as hardcoded constants. The i stands for the position of the bit in the fractional binary representation. E.g. if we were to match the very first bit, we would multiply by the square root of 2, i.e. ~1.4142.
Arbitrary Base
Given the basic logarithm rule:
x = b^(log_b(x))
We can deduce:
x^y = b^(y*log_b(x))
Applying to base 2:
x^y = 2^(y*log_2(x))
We already have an implementation for exp_2 and log_2 is also offered by ABDKMath64x64.
Trade Offs
What I don't like about Mikhail's solution:
- Requires users to work with binary numbers
- Precision is limited to int128, doesn't go to int256 or near that