简体   繁体   English

用小数写自己的指数幂函数

[英]Writing your own exponential power function with decimals

So, I want to write a function in code using some sort of algorithm to calculate any number to any power, including decimals. 因此,我想使用某种算法在代码中编写一个函数,以计算任意幂的任何数字,包括小数。 I use JavaScript and it already has an inbuilt pow function: 我使用JavaScript,它已经具有内置的pow函数:

Math.pow(2, 0.413) // 2^0.413 = 1.331451613236371, took under 1 second.

Now I want to write my own like this: 现在我要这样写我自己的:

function pow(x, y) {
    // Algorithm
}

This is a function that calculates the square root of any number (x^0.5), and it's very accurate with only 10 loops: 此函数可计算任何数字(x ^ 0.5)的平方根,并且只有10个循环的精度很高:

function sqrt(x, p) { // p = precision (accuracy)
    var a = 1;
    var b = x;

    while (p--) {
        a = (a + b) / 2
        b = x / a
    }

    return a
}

Is there any simple formula to calculate any exponential? 有没有简单的公式可以计算指数?

If there isn't a simple one, is there a hard one? 如果没有简单的方法,那么有没有困难的方法?

If the solution is slow, how can JavaScript's pow estimate under a single second? 如果解决方案很慢,那么如何在一秒钟内估算JavaScript的功耗?

Heres a nice algorithm for positive integer powers, it starts by dealing with some simple cases and then uses a loop testing the binary bits of the exponent. 对于正整数幂,这是一个不错的算法,它从处理一些简单的情况开始,然后使用循环测试指数的二进制位。 For example to find 3^11 11 in binary is 1011 so the stages in the loop are 例如,以二进制找到3^11 11是1011,因此循环中的阶段是

  • bitMask = 1011, evenPower = 3, result = 3 bitMask = 1011,evenPower = 3,结果= 3
  • bitMask = 101, evenPower = 3*3 = 9, result = 3*9 = 27 bitMask = 101,evenPower = 3 * 3 = 9,结果= 3 * 9 = 27
  • bitMask = 10, evenPower = 9*9 = 81, result = 27 bitMask = 10,evenPower = 9 * 9 = 81,结果= 27
  • bitMask = 1, evenPower = 81*81 = 6561, result = 27*6561 = 177147 bitMask = 1,evenPower = 81 * 81 = 6561,结果= 27 * 6561 = 177147

That is the evenPower squares at each loop, and the result gets multiplied by the evenPower if the bottom bit is 1. The code has been lifted from Patricia Shanahan's method http://mindprod.com/jgloss/power.html which in turn has its roots in Kunth and can be traced back to 200 BC in india. 那是每个循环的evenPower平方,如果最低位是1,则结果乘以evenPower。代码已从Patricia Shanahan的方法http://mindprod.com/jgloss/power.html提取,该方法又具有它的根源于Kunth,可以追溯到公元前200年的印度。

/**
 * A fast routine for computing integer powers.
 * Code adapted from {@link <a href="http://mindprod.com/jgloss/power.html">efficient power</a>} by Patricia Shanahan pats@acm.org
 * Almost identical to the method Knuth gives on page 462 of The Art of Computer Programming Volume 2 Seminumerical Algorithms.
 * @param l number to be taken to a power.
 * @param n power to take x to. 0 <= n <= Integer.MAX_VALUE
 * Negative numbers will be treated as unsigned positives.
 * @return x to the power n
 * 
 */
public static final double power(double l,int n)
{
    assert n>=0;

    double x=l;
    switch(n){
    case 0: x = 1.0; break;
    case 1: break;
    case 2: x *= x; break;
    case 3: x *= x*x; break;
    case 4: x *= x; x *= x; break;
    case 5: { double y = x*x; x *= y*y; } break;
    case 6: { double y = x*x; x = y*y*y; } break;
    case 7: { double y = x*x; x *= y*y*y; } break;
    case 8: x *= x; x *= x; x *= x; break;
    default:
    {
        int bitMask = n;
        double evenPower = x;
        double result;
        if ( (bitMask & 1) != 0 )
            result = x;
        else
            result = 1;
        bitMask >>>= 1;
        while ( bitMask != 0 ) {
            evenPower *= evenPower;
            if ( (bitMask & 1) != 0 )
                result *= evenPower;
            bitMask >>>= 1;
        } // end while
        x = result;
    }
    }
    return x;
}

For a real exponent you will basically need ways of finding exp and log. 对于真正的指数,您基本上需要找到exp和log的方法。 You can use Taylor series which are the simplest to get but there are much better method. 您可以使用最简单的泰勒级数,但有更好的方法。 We have 我们有

exp(x) = 1 + x + x^2/2 + x^3/6 + x^4/24 + x^5/120 + x^6/6! + ...

ln(1+x) = x - x^2 /2 + x^3 /3 - x^4 / 4 + x^5 / 5 - x^6/6 + ... |x|<1

To find x^y note ln(x^y) = y*ln(x) . 为了找到x ^ y,请注意ln(x^y) = y*ln(x) Now we need to get the argument in the right range so we can use our power series. 现在我们需要将参数设置在正确的范围内,以便可以使用幂级数。 Let x = m * 2^ex, the mantissa and exponent chosen so 1/sqrt(2)<= m < sqrt(2) and ln(m*2^ex) = ln(m) + ex*ln(2) . 令x = m * 2 ^ ex,选择尾数和指数,因此1 / sqrt(2)<= m <sqrt(2)且ln(m*2^ex) = ln(m) + ex*ln(2) Let h = m-1 and find ln(1+h). 令h = m-1并找到ln(1 + h)。

Using java and floats as there is an easy way to find the internals of the IEEE representation (I've used float as there are fewer bits to cope with) 使用java和floats有一种简单的方法来查找IEEE表示的内部结构(我使用了float因为有较少的位要应付)

int b = Float.floatToIntBits(x);
int sign = (b & 0x80000000) == 0 ? 1 : -1;
int mattissa = b & 0x007fffff;
int ex = ((b & 0x7f800000) >> 23 ) - 127;

in javascript it might be easiest to us Number.toExponential and parse the results. 在javascript中,对我们来说Number.toExponential并解析结果可能是最简单的。

Next construct a number z in the desired range 1/sqrt(2) < z < sqrt(2) 接下来构造期望范围1 / sqrt(2)<z <sqrt(2)中的数字z

int bits = mattissa | 0x3f800000;
float z = Float.intBitsToFloat(bits);
if(z>root2) { 
    z = z/2;
    ++ex;
}

Use this function to find the log of 1+x using a taylor series 使用此函数使用泰勒级数查找1 + x的对数

static float ln1px(float x) {
    float x_2 = x*x; // powers of x
    float x_3 = x_2 * x;
    float x_4 = x_3 * x;
    float x_5 = x_4 * x;
    float x_6 = x_5 * x; 
    float res = x - x_2 /2 + x_3 /3 - x_4 / 4 + x_5 / 5 - x_6/6;
    return res;
}

this seems to be good to three significant figures, often much better when x is close to 0. 这似乎对三个有效数字都很好,通常在x接近0时好得多。

The log of our number x can then be found 然后可以找到我们的数字x的对数

float w = z - 1;
float ln_z = ln1px(w);
float ln_x = ln_z + ln2 * ex;
System.out.println("ln "+ln_x+"\t"+Math.log(x));

Now to the actual power if we write y = n + a where n is an integer and a is fractional. 现在我们将y = n + a写成实际乘方,其中n是整数,a是分数。 So x^y=x^(n+a) = x^n * x^a . 所以x^y=x^(n+a) = x^n * x^a use the first algorithm in this answer to find the x^n . 在此答案中使用第一种算法找到x^n Writing x=m*2^ex then ln((m*2^ex)^a) = y ln(m) + y ex*ln(2) and x=m*2^ex然后ln((m*2^ex)^a) = y ln(m) + y ex*ln(2)

x^a=exp(ln((m*2^ex)^a)) = exp(a * ln(m)) * exp(a * ln(2))^ex

the two exponential terms have fairly small values so the taylor series should be good. 这两个指数项的值相当小,因此taylor级数应该很好。

We need one function for the taylor series of the exponential function 指数函数的泰勒级数需要一个函数

static float exp(float x) {
    float x_2 = x*x; // powers of x
    float x_3 = x_2 * x;
    float x_4 = x_3 * x;
    float x_5 = x_4 * x;
    float x_6 = x_5 * x; 
    float res = 1+ x + x_2 /2 + x_3 /6 + x_4 / 24 + x_5 / 120 + x_6/ 720;
    return res;
}

finally we can put the pieces together 最终我们可以将各个部分放在一起

// Get integer and fractional parts of y
int n = (int) Math.floor(y);
float a = y-n;

float x_n = power(x,n);         // x^n
float a_ln_m = a * ln_z;        // ln(m^a) = a ln(m)
float a_ln_2 = a * ln2;         // ln(2^a) = a ln(2)
float m_a = exp(a_ln_m);        // m^a = exp(a ln(m))
float _2_a = exp(a_ln_2);       // 2^a = exp(a ln(2))
float _2_a_ex = power(_2_a,ex); // (2^ex)^a = 2^(a*ex) = (2^a)^ex 
float x_a = m_a * _2_a_ex;      // x^a = m^a * 2^(a*ex)

float x_y = x_n * x_a;          // x^y = x^n * x^a

System.out.println("x^y "+x_y+"\t"+Math.pow(x,y));

That should be the complete program, you need some smarts to cope with negative arguments etc. 那应该是完整的程序,您需要一些技巧来应对否定参数等。

Note this is not particularly accurate as I've only used a few terms of the taylor series. 请注意,这并不是特别准确,因为我只使用了taylor系列的几个术语。 Other SO questions have more detailed answers How can I write a power function myself? 其他SO问题有更详细的答案我如何自己编写幂函数?

I checked this post, but it worked only for whole numbers (1,2,3... not 0.1, 0.3...) 我检查了这篇文章,但它仅适用于整数(1,2,3 ...而不是0.1、0.3 ...)

Recursive power function: Why does this work if there's no initial return value? 递归幂函数:如果没有初始返回值,为什么这样做有效?

Then, 然后,

I got this from here: Algorithm for pow(float, float) 我是从这里得到的: pow(float,float)的算法

function power(x,n) {
    if(n === 0) return 1;
    if(n === -1) return 1/x;
    if(n === 1) return x;
    return Math.exp(n*Math.log(x))
}

console.log(power(2,3.5));

I added some basic checks (n===0)... To fasten things up in case. 我添加了一些基本检查(n === 0)...以防万一。

Flexo sums it up: 柔印总结一下:

The general algorithm tends to be computing the float power as the combination of the integer power and the remaining root. 通用算法倾向于将浮点功率作为整数幂和剩余根的组合来计算。 The integer power is fairly straightforward, the root can be computed using either Newton - Raphson method or Taylor series. 整数幂很简单,可以使用牛顿-拉夫森方法或泰勒级数计算根。 IIRC numerical recipes in C has some text on this. C中的IIRC数字配方对此有一些说明。 There are other (potentially better) methods for doing this too, but this would make a reasonable starting point for what is a surprisingly complex problem to implement. 也有其他(可能更好)的方法来执行此操作,但这将为实现一个令人惊讶的复杂问题提供一个合理的起点。 Note also that some implementations use lookup tables and a number of tricks to reduce the computation required. 还要注意,某些实现使用查找表和许多技巧来减少所需的计算。

http://mathworld.wolfram.com/NewtonsMethod.html http://mathworld.wolfram.com/NewtonsMethod.html

http://mathworld.wolfram.com/TaylorSeries.html http://mathworld.wolfram.com/TaylorSeries.html

https://en.wikipedia.org/wiki/Logarithm#Power_series https://zh.wikipedia.org/wiki/对数#Power_series

https://rads.stackoverflow.com/amzn/click/0521431085 https://rads.stackoverflow.com/amzn/click/0521431085

Those are some really nice examples, here is a simpler one too. 这些是一些非常好的示例,这也是一个简单的示例。

function exponential(a,b){
    var c = 1;
    for(var i=1; i<=b; i++){
        c = c * a;
    }
    return c;
}

now call the function: 现在调用函数:

exponential(2,4);

Edit: It only works on integer, but it's simple and quick. 编辑:它仅适用于整数,但它简单而又快捷。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM