我如何自己编写电源功能?

时间:2010-05-21 14:01:53

标签: c++ math floating-point

我一直想知道如何制作一个自己计算功率的函数(例如2 3 )。在大多数语言中,这些都包含在标准库中,大部分都是pow(double x, double y),但我怎么能自己编写呢?

我在考虑for loops,但它认为我的大脑陷入了一个循环(当我想用非整数指数来做功率时,如5 4.5 或负数2 -21 )我疯了;)

那么,我怎样才能编写一个计算实数幂的函数呢?感谢


哦,也许重要的是要注意:我不能使用使用权力的函数(例如exp),这会使这最终变得无用。

14 个答案:

答案 0 :(得分:48)

负功率不是问题,它们只是正功率的倒数(1/x)。

浮点功率稍微复杂一些;如你所知,分数幂相当于一个根(例如x^(1/2) == sqrt(x)),你也知道相同基数的乘法相当于添加它们的指数。

综上所述,您可以:

  • Decompose the exponent in a integer part and a rational part
  • 使用循环计算整数幂(您可以优化分解因子并重复使用部分计算)。
  • 使用您喜欢的任何算法计算根(任何迭代逼近,如二分法或牛顿法都可以)。
  • 乘以结果。
  • 如果指数为负数,则应用反向。

示例:

2^(-3.5) = (2^3 * 2^(1/2)))^-1 = 1 / (2*2*2 * sqrt(2))

答案 1 :(得分:22)

B = Log -1 (Log(A)* B)

编辑:是的,这个定义确实提供了一些有用的东西。例如,在x86上,它几乎直接转换为FYL2X(Y * Log 2 (X))和F2XM1(2 x - 1):

fyl2x
fld st(0)
frndint
fsubr st(1),st
fxch st(1)
fchs
f2xmi
fld1
faddp st(1),st
fscale
fstp st(1) 

代码的结束时间比您预期的要长一些,主要是因为F2XM1仅适用于-1.0..1.0范围内的数字。 fld st(0)/frndint/fsubr st(1),st部分减去整数部分,因此我们只剩下分数。我们对此应用F2XM1,重新添加1,然后使用FSCALE来处理取幂的整数部分。

答案 2 :(得分:19)

通常,数学库中pow(double, double)函数的实现基于标识:

pow(x,y) = pow(a, y * log_a(x))

使用此标识,您只需要知道如何将单个数字a提升为任意指数,以及如何采用对数基数a。您已经将复杂的多变量函数有效地转换为单个变量和乘法的两个函数,这很容易实现。最常选择的a值为e2 - e,因为e^xlog_e(1+x)具有一些非常好的数学属性,和2因为它在浮点运算中有一些很好的属性。

这样做的好处是(如果你想要获得完全准确性),你需要计算log_a(x)项(及其产品y),以获得比浮动更高的精度 - xy的点表示。例如,如果xy是双倍的,并且您希望得到高精度结果,那么您需要提出一些方法来存储中间结果(并进行算术运算) - 精确格式。英特尔x87格式是常见的选择,64位整数也是常见的选择(尽管如果你真的想要一个高质量的实现,你需要做一些96位整数计算,这在某些方面有点痛苦语言)。如果实现powf(float,float),处理此问题要容易得多,因为这样您就可以使用double进行中间计算。如果你想使用这种方法,我建议从那开始。


我概述的算法并不是计算pow的唯一可行方法。它仅仅是最适合于提供满足固定先验精度限制的高速结果。它在其他一些情况下不太适合,并且实际上比其他人建议的重复平方[root] -ing算法更难实现。

如果要尝试重复的square [root]算法,首先要编写一个仅使用重复平方的无符号整数幂函数。一旦掌握了减少算例的算法,你就会发现扩展它以处理小数指数非常简单。

答案 3 :(得分:9)

有两种不同的处理方式:整数指数和小数指数。

对于整数指数,您可以通过平方来使用取幂。

def pow(base, exponent):
    if exponent == 0:
        return 1
    elif exponent < 0:
        return 1 / pow(base, -exponent)
    elif exponent % 2 == 0:
        half_pow = pow(base, exponent // 2)
        return half_pow * half_pow
    else:
        return base * pow(base, exponent - 1)

第二个“elif”是与天真的pow功能区别开来的。它允许函数进行O(log n)递归调用而不是O(n)。

对于小数指数,您可以使用标识a ^ b = C ^(b * log_C(a))。取C = 2很方便,所以a ^ b = 2 ^(b * log2(a))。这减少了为2 ^ x和log2(x)编写函数的问题。

采用C = 2很方便的原因是浮点数存储在base-2浮点中。 log2(a * 2 ^ b)= log2(a)+ b。这样可以更容易地编写log2函数:您不需要对每个正数都准确,只需在区间[1,2]上。类似地,要计算2 ^ x,您可以乘以2 ^(x的整数部分)* 2 ^(x的小数部分)。第一部分存储在浮点数中是微不足道的,对于第二部分,您只需要在区间[0,1]上使用2 ^ x函数。

困难的部分是找到2 ^ x和log2(x)的良好近似值。一种简单的方法是使用Taylor series

答案 4 :(得分:7)

根据定义:

  

a ^ b = exp(b ln(a))

其中exp(x) = 1 + x + x^2/2 + x^3/3! + x^4/4! + x^5/5! + ...

其中n! = 1 * 2 * ... * n

在实践中,您可以存储前10个1/n!值的数组,然后近似

exp(x) = 1 + x + x^2/2 + x^3/3! + ... + x^10/10!

因为10!是一个巨大的数字,所以1/10!非常小(2.7557319224⋅10^ -7)。

答案 5 :(得分:4)

Wolfram functions提供了各种计算能力的公式。其中一些实施起来非常简单。

答案 6 :(得分:4)

对于正整数幂,请查看exponentiation by squaringaddition-chain exponentiation

答案 7 :(得分:2)

使用三个自我实现的函数iPow(x, n)Ln(x)Exp(x),我能够计算fPow(x, a),x和双倍 。下面的函数都没有使用库函数,只是迭代。

关于实施的功能的一些解释:

(1)iPow(x, n):x是double,n是int。这是一个简单的迭代,因为n是一个整数。

(2)Ln(x):此函数使用泰勒级数迭代。迭代中使用的系列是Σ (from int i = 0 to n) {(1 / (2 * i + 1)) * ((x - 1) / (x + 1)) ^ (2 * n + 1)}。符号^表示在第一个函数中实现的幂函数Pow(x, n),它使用简单的迭代。

(3)Exp(x):此函数再次使用泰勒级数迭代。迭代中使用的系列是Σ (from int i = 0 to n) {x^i / i!}。这里,^表示幂函数,但是通过调用第一Pow(x, n)函数计算 ;相反,它使用d *= x / i在第3个函数中与factorial同时实现。我感觉我不得不使用这个技巧,因为在这个函数中,迭代相对于其他函数需要更多的步骤,并且因子(i!)在大多数时候都会溢出。为了确保迭代不会溢出,此部分中的幂函数与阶乘同时迭代。这样,我克服了溢出。

(4)fPow(x, a) x和a都是双打。此函数除了调用上面实现的其他三个函数之外什么都不做。这个函数的主要思想取决于一些微积分:fPow(x, a) = Exp(a * Ln(x))。现在,我已经完成了迭代的所有函数iPowLnExp

n.b。我使用constant MAX_DELTA_DOUBLE来决定停止迭代的步骤。我把它设置为1.0E-15,这对于双打来说似乎是合理的。因此,如果(delta < MAX_DELTA_DOUBLE)迭代停止如果您需要更高的精度,则可以使用long double并将MAX_DELTA_DOUBLE的常量值减小到1.0E-18,例如(1.0E-) 18将是最低限度。)

这是代码,对我有用。

#define MAX_DELTA_DOUBLE 1.0E-15
#define EULERS_NUMBER 2.718281828459045

double MathAbs_Double (double x) {
    return ((x >= 0) ? x : -x);
}

int MathAbs_Int (int x) {
    return ((x >= 0) ? x : -x);
}

double MathPow_Double_Int(double x, int n) {
    double ret;
    if ((x == 1.0) || (n == 1)) {
        ret = x;
    } else if (n < 0) {
        ret = 1.0 / MathPow_Double_Int(x, -n);
    } else {
        ret = 1.0;
        while (n--) {
            ret *= x;
        }
    }
    return (ret);
}

double MathLn_Double(double x) {
    double ret = 0.0, d;
    if (x > 0) {
        int n = 0;
        do {
            int a = 2 * n + 1;
            d = (1.0 / a) * MathPow_Double_Int((x - 1) / (x + 1), a);
            ret += d;
            n++;
        } while (MathAbs_Double(d) > MAX_DELTA_DOUBLE);
    } else {
        printf("\nerror: x < 0 in ln(x)\n");
        exit(-1);
    }
    return (ret * 2);
}

double MathExp_Double(double x) {
    double ret;
    if (x == 1.0) {
        ret = EULERS_NUMBER;
    } else if (x < 0) {
        ret = 1.0 / MathExp_Double(-x);
    } else {
        int n = 2;
        double d;
        ret = 1.0 + x;
        do {
            d = x;
            for (int i = 2; i <= n; i++) {
                d *= x / i;
            }
            ret += d;
            n++;
        } while (d > MAX_DELTA_DOUBLE);
    }
    return (ret);
}

double MathPow_Double_Double(double x, double a) {
    double ret;
    if ((x == 1.0) || (a == 1.0)) {
        ret = x;
    } else if (a < 0) {
        ret = 1.0 / MathPow_Double_Double(x, -a);
    } else {
        ret = MathExp_Double(a * MathLn_Double(x));
    }
    return (ret);
}

答案 8 :(得分:1)

这是一项有趣的练习。以下是一些建议,您应该按此顺序尝试:

  1. 使用循环。
  2. 使用递归(不是更好,但有趣的是)
  3. 使用分而治之的方法,大大优化您的递归 技术
  4. 使用对数

答案 9 :(得分:1)

有效计算正整数幂的更好算法是重复平方基,同时跟踪额外的余数被乘数。以下是Python中的示例解决方案,应该相对容易理解并转换为您的首选语言:

def power(base, exponent):
  remaining_multiplicand = 1
  result = base

  while exponent > 1:
    remainder = exponent % 2
    if remainder > 0:
      remaining_multiplicand = remaining_multiplicand * result
    exponent = (exponent - remainder) / 2
    result = result * result

  return result * remaining_multiplicand

要使它处理负指数,你所要做的就是计算正数并用结果除1,这应该是对上面代码的简单修改。分数指数要困难得多,因为它意味着基本上计算基数的第n个根,其中n = 1/abs(exponent % 1)并将结果乘以整数部分幂计算的结果:

power(base, exponent - (exponent % 1))

您可以使用牛顿方法计算根到所需的准确度。查看wikipedia article on the algorithm

答案 10 :(得分:1)

你可以找到 pow 这样的功能:

static double pows (double p_nombre, double p_puissance)
{
    double nombre   = p_nombre;
    double i=0;
    for(i=0; i < (p_puissance-1);i++){
          nombre = nombre * p_nombre;
       }
    return (nombre);
}

你可以找到 floor 这样的功能:

static double floors(double p_nomber)
{
    double x =  p_nomber;
    long partent = (long) x; 

    if (x<0)
    {
        return (partent-1);
    }
    else
    {
        return (partent);
    }
}

祝你好运

答案 11 :(得分:0)

我使用定点长算术,我的pow是基于log2 / exp2。数字包括:

  • int sig = { -1; +1 } signum
  • DWORD a[A+B]号码
  • ADWORD s的数字,用于数字的整数部分
  • B是小数部分DWORD的数量

我的简化解决方案就是:

//---------------------------------------------------------------------------
longnum exp2 (const longnum &x)
{
    int i,j;
    longnum c,d;
    c.one();
    if (x.iszero()) return c;
    i=x.bits()-1;
    for(d=2,j=_longnum_bits_b;j<=i;j++,d*=d)
    if (x.bitget(j))
    c*=d;
    for(i=0,j=_longnum_bits_b-1;i<_longnum_bits_b;j--,i++)
    if (x.bitget(j))
    c*=_longnum_log2[i];
    if (x.sig<0) {d.one(); c=d/c;}
    return c;
}
//---------------------------------------------------------------------------
longnum log2 (const longnum &x)
{
    int i,j;
    longnum c,d,dd,e,xx;
    c.zero(); d.one(); e.zero(); xx=x;
    if (xx.iszero()) return c; //**** error: log2(0) = infinity
    if (xx.sig<0) return c; //**** error: log2(negative x) ... no result possible
    if (d.geq(x,d)==0) {xx=d/xx; xx.sig=-1;}
    i=xx.bits()-1;
    e.bitset(i); i-=_longnum_bits_b;
    for (;i>0;i--,e>>=1) // integer part
    {
        dd=d*e;
        j=dd.geq(dd,xx);
        if (j==1) continue; // dd> xx
        c+=i; d=dd;
        if (j==2) break; // dd==xx
    }
    for (i=0;i<_longnum_bits_b;i++) // fractional part
    {
        dd=d*_longnum_log2[i];
        j=dd.geq(dd,xx);
        if (j==1) continue; // dd> xx
        c.bitset(_longnum_bits_b-i-1); d=dd;
        if (j==2) break; // dd==xx
    }
    c.sig=xx.sig;
    c.iszero();
    return c;
}
//---------------------------------------------------------------------------
longnum pow (const longnum &x,const longnum &y)
{
    //x^y = exp2(y*log2(x))
    int ssig=+1; longnum c; c=x;
    if (y.iszero()) {c.one(); return c;} // ?^0=1
    if (c.iszero()) return c; // 0^?=0
    if (c.sig<0)
    {
        c.overflow(); c.sig=+1;
        if (y.isreal()) {c.zero(); return c;} //**** error: negative x ^ noninteger y
        if (y.bitget(_longnum_bits_b)) ssig=-1;
    }
    c=exp2(log2(c)*y); c.sig=ssig; c.iszero();
    return c;
}
//---------------------------------------------------------------------------

其中:

_longnum_bits_a = A*32
_longnum_bits_b = B*32
_longnum_log2[i] = 2 ^ (1/(2^i))  ... precomputed sqrt table 
_longnum_log2[0]=sqrt(2)  
_longnum_log2[1]=sqrt[tab[0]) 
_longnum_log2[i]=sqrt(tab[i-1])
longnum::zero() sets *this=0
longnum::one() sets *this=+1
bool longnum::iszero() returns (*this==0)
bool longnum::isnonzero() returns (*this!=0)
bool longnum::isreal() returns (true if fractional part !=0)
bool longnum::isinteger() returns (true if fractional part ==0)
int longnum::bits() return num of used bits in number counted from LSB
longnum::bitget()/bitset()/bitres()/bitxor() are bit access
longnum.overflow() rounds number if there was a overflow X.FFFFFFFFFF...FFFFFFFFF??h  -> (X+1).0000000000000...000000000h
int longnum::geq(x,y)  is comparition |x|,|y| returns 0,1,2 for (<,>,==)

所有你需要理解的代码是二进制形式的数字由2的幂的总和组成,当你需要计算2 ^ num然后它可以被重写为

  • 2^(b(-n)*2^(-n) + ... + b(+m)*2^(+m))

其中n是小数位,m是整数位。以二进制形式乘以/除2是简单的位移,所以如果你把它们放在一起就得到exp2的代码类似于我的代码。 log2基于二进制搜索...将结果位从MSB更改为LSB,直到它与搜索值匹配(与快速sqrt计算非常相似的算法)。希望这有助于澄清事情...

答案 12 :(得分:0)

其他答案中给出了很多方法。这是我认为在整体力量的情况下可能有用的东西。

n x 的整数幂x的情况下,直接的方法将采用x-1乘法。为了优化这一点,我们可以使用动态编程并重用早期的乘法结果来避免所有x乘法。例如,在5 9 中,我们可以说,批次为3 ,即计算5 3 一次,得到125然后立方体125使用相同的逻辑,在过程中只采用4个乘法,而不是以简单的方式进行8次乘法。

问题是批次b的理想大小是多少,因此乘法次数最小。那么让我们为此写出等式。如果f(x,b)是表示使用上述方法计算n x 所需的乘法数的函数,那么

> f(x,b) = (x/b - 1) + (b-1)

说明:一批p数的乘积将进行p-1次乘法。如果我们将x乘法划分为b个批次,则每个批次中需要(x / b)-1个乘法,并且所有b个批次都需要b-1个乘法。

现在我们可以计算这个函数关于b的一阶导数,并将其等于0,得到b乘以最少的乘法。

f'(x,b) = -x/b<sup>2</sup> + 1 = 0

enter image description here

现在将b的这个值放回到函数f(x,b)中以获得最少的乘法次数:

enter image description here

对于所有正x,这个值比直接方式的乘法小。

答案 13 :(得分:0)

也许您可以使用taylor系列扩展。函数的泰勒级数是项的无限和,这些项在一个点上以函数的导数表示。对于大多数常见函数,此点附近的函数及其泰勒级数的总和相等。泰勒(Taylor)的系列是以布鲁克·泰勒(Brook Taylor)的名字命名的,布鲁克·泰勒(Brook Taylor)于1715年对其进行了介绍。