5

With solidity, we cannot directly calculate 0.1 or 0.001. We usually use a decimal multiplier and divisor integer to calculate numbers like this:

0.01 = 1 / 100

When the number is more than 1, it is relatively easy because we just repeat the multiplication:

function(uint base, uint exponent) external {
    for (uint256 i = 1; i < exponent; i++) 
        uint256 z = base;
        z = z * base; 
    }

Or just:

z ** exponent

However, when we want to use fractional numbers as exponents, those functions won't work. Can anybody think of a nice way to do this?

3
  • Maintain the value as a pair of integers (numerator and denominator) right up until the point where you have to return it. Alternatively, return it as a pair of integers and let the receiver handle that. Feb 16 '20 at 17:03
  • Thanks, but seems like this easily causes integer overflow because it exceeds 2^256 when calculating numbers like 10^100
    – kohshiba
    Feb 17 '20 at 1:35
  • @kohshiba There are a couple of libraries that implement some floting point operations: github.com/cag/solidity-float-point-calculation, github.com/abdk-consulting/abdk-libraries-solidity. Unless you have a good reason I'd try to re-write the equation to use integers.
    – Ismael
    Feb 17 '20 at 17:10
4

Binary Fractions

I currently know of only one solution, authored by Mikhail Vladimirov. It works by computing the input as a binary fraction.

However, I am still interested in other potential solutions, which is why I awarded a bounty.

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));
    }
}

I skipped a big chunk of the function body, for brevity.

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:

  1. Requires users to work with binary numbers
  2. Precision is limited to int128, doesn't go to int256 or near that
1
0

Paul's answer is very good. If you want to approximate an exponential function, I used a series of linear functions with a target half-life:

function getPrice(uint256 value, uint256 t, uint256 halfLife) public pure returns (uint256 price) {
    price = value >> (t / halfLife);
    price -= price * (t % halfLife) / halfLife / 2;
}

Half-life in my code was the number of seconds in two days.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.