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

时间:2014-06-01 17:26:37

标签: javascript algorithm math

所以,我想在代码中编写一个函数,使用某种算法来计算任何幂的任何数,包括小数。我使用JavaScript,它已经具有内置的pow功能:

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

现在我想写这样的话:

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

这是一个计算任意数字(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
}

是否有任何简单的公式来计算任何指数?

如果没有一个简单的那个,那会有一个难的吗?

如果解决方案很慢,那么JavaScript的功耗估算如何在一秒钟内完成?

3 个答案:

答案 0 :(得分:3)

对于正整数幂来说,这是一个很好的算法,它首先处理一些简单的情况,然后使用循环测试指数的二进制位。例如,要查找二进制中的3^11 11是1011,那么循环中的阶段是

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

这就是每个循环中的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;
}

对于真正的指数,您基本上需要找到exp和log的方法。您可以使用最简单的泰勒系列,但有更好的方法。我们有

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

找到x ^ y note ln(x^y) = y*ln(x)。现在我们需要在正确的范围内获得参数,以便我们可以使用我们的幂级数。令x = m * 2 ^ ex,选择的尾数和指数为1 / sqrt(2)&lt; = m&lt; sqrt(2)和ln(m*2^ex) = ln(m) + ex*ln(2)。设h = m-1,求ln(1 + h)。

使用java和floats,因为有一种简单的方法可以找到IEEE表示的内部结构(我已经使用了浮点数,因为需要更少的位来处理)

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

在javascript中对我们来说最简单Number.toExponential并解析结果。

接下来,在期望的范围1 / sqrt(2)<1中构建数字z。 z&lt; SQRT(2)

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

使用此功能可以使用泰勒系列

查找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;
}

这似乎对三个重要数字有好处,当x接近0时通常要好得多。

然后可以找到我们的数字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));

如果我们写y = n + a,那么现在达到实际功率,其中n是整数,a是小数。所以 x^y=x^(n+a) = x^n * x^a。使用此答案中的第一个算法来查找x^n。写x=m*2^ex然后ln((m*2^ex)^a) = yln(m) + yex*ln(2)

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

两个指数项具有相当小的值,因此泰勒系列应该是好的。

我们需要一个函数用于指数函数的泰勒级数

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

最后我们可以把各个部分放在一起

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

这应该是完整的程序,你需要一些智慧来应对负面论点等。

请注意,这并不是特别准确,因为我只使用了泰勒系列的几个术语。其他SO问题有更详细的答案How can I write a power function myself?

答案 1 :(得分:1)

我检查了这篇文章,但它仅适用于整数(1,2,3 ......不是0.1,0.3 ......)

Recursive power function: Why does this work if there's no initial return value?

然后,

我从这里得到了这个:Algorithm for 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));

我添加了一些基本检查(n === 0)......为了以防万一。

Flexo总结一下:

  

通用算法倾向于将浮动功率计算为   整数幂与剩余根的组合。整数   功率相当简单,可以使用任何一个来计算根   牛顿 - 拉普森方法或泰勒系列。 IIRC中的IIRC数字配方   有一些文字。还有其他(可能更好)的方法   这样做,但这将是一个合理的起点   这是一个令人惊讶的复杂问题。另请注意   一些实现使用查找表和一些技巧   减少所需的计算量。

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

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

http://en.wikipedia.org/wiki/Logarithm#Power_series

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

答案 2 :(得分:1)

这些是一些非常好的例子,这里也是一个更简单的例子。

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

现在调用函数:

exponential(2,4);

编辑:它只适用于整数,但它简单快捷。