Solidity – Efficient Ways to Compute Exponentiation of Fractional Numbers

evmsolidity

First off – not the same question as this (which is great). I need the exponent to be fractional as well. Something like 2.5^0.75

Best Answer

The code of a good solution:

function power(uint256 _baseN, uint256 _baseD, uint32 _expN, uint32 _expD) internal view returns (uint256, uint8) {
    assert(_baseN < MAX_NUM);

    uint256 baseLog;
    uint256 base = _baseN * FIXED_1 / _baseD;
    if (base < OPT_LOG_MAX_VAL) {
        baseLog = optimalLog(base);
    }
    else {
        baseLog = generalLog(base);
    }

    uint256 baseLogTimesExp = baseLog * _expN / _expD;
    if (baseLogTimesExp < OPT_EXP_MAX_VAL) {
        return (optimalExp(baseLogTimesExp), MAX_PRECISION);
    }
    else {
        uint8 precision = findPositionInMaxExpArray(baseLogTimesExp);
        return (generalExp(baseLogTimesExp >> (MAX_PRECISION - precision), precision), precision);
    }
}

Full working code at: https://github.com/Muhammad-Altabba/solidity-toolbox/blob/master/contracts/FractionalExponents.sol

Some explanation

A floating point number x can be represented in two numbers: a/b

 if x = a/b and y = c/d, then x ^ y = (a/b) ^ (c/d)

The problem

The problem is that if the a code is written as (a/b) ^ (c/d), the accuracy of the result would be a mess. Because the division of a/b and c/d would smash the floating point.

Going into another try, the expression (a/b) ^ (c/d) could be evaluated as a ^ (c/d) / b ^ (c/d) or (a^c + a^(1/d)) / (b^c + b^(1/d)). The problem with this is that the powering a to c or even to c/d could easily overflow uint256! Even though, the final resulting value could be a small uint8. (this is explained here)

The Solution

There is an approximation for x ^ y as follow:

x ^ y  = e ^ (log(x) * y)

Actually, the method above depend on this equation to compute the power function. As follow:

(_baseN / _baseD) ^ (_expN /_expD) = e ^ (log(base) * exp)

Where base = _baseN / _baseD (note that FIXEX_1 that is used in the code, is just used to shift the number of left in order to provide better accuracy of the division. And exp = _expN /_expD.

The key point here is calculating the log(base) * exp before powering to e, will prevent overflow compared to the early discussed formula the can easily produce overflow: a ^ (c/d) / b ^ (c/d) or (a^c + a^(1/d)) / (b^c + b^(1/d)).

However, for optimalLog vs. generalLog and optimalExp vs. generalExp that is used in the provided code, they is about calculating the log and e ^ either in an optimal or an approximate calculation.


Thanks leonprou for his valuable comments on the question.

Related Topic