用小数来写指数幂函数



所以,我想用代码写一个函数,使用某种算法来计算任何数字的任何幂,包括小数。我使用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^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

这是每个循环的偶数幂平方,如果底部位为1,则结果乘以偶数幂。这个密码是从帕特丽夏·沙纳汉的方法中提取出来的http://mindprod.com/jgloss/power.html,而这种方法又起源于昆斯,可以追溯到公元前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,请注意ln(x^y) = y*ln(x)。现在我们需要让实参在正确的范围内这样我们就可以用幂级数了。设x = m * 2^ex,尾数和指数选择1/根号(2)<= m <sqrt(2)和ln(m*2^ex) = ln(m) + ex*ln(2)。令h>

使用java和浮点数,因为有一个简单的方法来找到IEEE表示的内部(我使用浮点数,因为有更少的位要处理)

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

在javascript中可能是最容易的。toExponential和解析结果。

在期望的范围内构造一个数字z 1/sqrt(2)

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

用泰勒级数求log (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问题有更详细的答案我如何写一个幂函数自己?

这些都是很好的例子,这里也有一个更简单的例子。

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

现在调用函数:

exponential(2,4);

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

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

递归幂函数:如果没有初始返回值,为什么它可以工作?

,

我从这里得到了这个:算法为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)…把东西系牢,以防万一。

柔印总结:

一般算法倾向于将浮点幂作为整数次幂和余数根的组合。整数Power相当简单,根可以使用任何一种方法来计算牛顿-拉夫森方法或者泰勒级数。IIRC数值配方有一些关于这个的文字。还有其他(可能更好的)方法但这是一个合理的起点这是一个非常复杂的问题。还请注意一些实现使用查找表和一些技巧来减少所需的计算。

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

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

https://en.wikipedia.org/wiki/Logarithm Power_series

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

最新更新