' Grokkable'算法来理解指数是浮点的指数

时间:2015-04-25 04:22:29

标签: c# c++ c algorithm exponentiation

首先澄清:

  • 2 ^ 3 = 8.相当于2 * 2 * 2。容易。
  • 2 ^ 4 = 16.相当于2 * 2 * 2 * 2。也很容易。
  • 2 ^ 3.5 = 11.313708 ......呃,这并不容易理解。

我想要的是一个简单的算法,最清楚地显示2 ^ 3.5 = 11.313708。除了基本的加法,减法,乘法或除法运算符之外,最好不要使用任何函数。

代码当然不是必须快速,也不一定需要简短(尽管这会有所帮助)。别担心,可以近似于用户指定的精确度(也应该是算法的一部分)。我希望有一个二进制印章/搜索类型的东西,因为这很容易理解。

到目前为止,我已找到this,但最重要的答案远非从概念层面上理解。

越多越好,所以我可以尝试了解攻击问题的不同方法。

我对答案的语言偏好是C#/ C / C ++ / Java,或者我所关心的伪代码。

5 个答案:

答案 0 :(得分:4)

好的,让我们只使用二进制搜索,加法和乘法来实现pow(x,y)。

驾驶y低于1

首先,不要这样做:

pow(x, y) == pow(x*x, y/2)
pow(x, y) == 1/pow(x, -y)

这对于处理负指数非常重要,并且y位于1以下,事情开始变得有趣。这样可以减少查找pow(x, y)所在地0<y<1的问题。

实施sqrt

在这个答案中,我假设您知道如何执行sqrt。我知道sqrt(x) = x^(1/2),但只需使用二进制搜索即可使用y = sqrt(x)搜索功能查找y*y=x,例如:

#define EPS 1e-8

double sqrt2(double x) {
    double a = 0, b = x>1 ? x : 1; 
    while(abs(a-b) > EPS) {
        double y = (a+b)/2;
        if (y*y > x) b = y; else a = y;
    }
    return a;
}

找到答案

理由是,每个低于1的数字可以近似为分数之和1/2^x

0.875 = 1/2 + 1/4 + 1/8
0.333333... = 1/4 + 1/16 + 1/64 + 1/256 + ...

如果您找到这些分数,您实际上会发现:

x^0.875 = x^(1/2+1/4+1/8) = x^(1/2) * x^(1/4) * x^(1/8)

最终导致

sqrt(x) * sqrt(sqrt(x)) * sqrt(sqrt(sqrt(x)))

因此,实现(在C ++中)

#define EPS 1e-8

double pow2(double x, double y){
    if (x < 0 and abs(round(y)-y) < EPS) {
        return pow2(-x, y) * ((int)round(y)%2==1 ? -1 : 1);
    } else if (y < 0) {
        return 1/pow2(x, -y);
    } else if(y > 1) {
        return pow2(x * x, y / 2);
    } else {
        double fraction = 1;
        double result = 1;

        while(y > EPS) {
            if (y >= fraction) {
                y -= fraction;
                result *= x;
            }

            fraction /= 2;
            x = sqrt2(x);
        }
        return result;
    }
}

答案 1 :(得分:2)

你可以非常容易验证那个2 ^ 3.5 = 11.313708:检查11.313708 ^ 2 =(2 ^ 3.5)^ 2 = 2 ^ 7 = 128

我认为理解你实际上要做的计算的最简单方法是刷新你对对数的理解 - 一个起点是http://en.wikipedia.org/wiki/Logarithm#Exponentiation

如果你真的想用最少的技术来计算非整数幂,那么一种方法就是将它们表示为分母,其分母的幂为2,然后取大量的平方根。例如。 x ^ 3.75 = x ^ 3 * x ^(1/2)* x ^(1/4)然后x ^(1/2)= sqrt(x),x ^(1/4)= sqrt(sqrt(x) ))等等。

这是另一种方法,基于验证猜测的想法。给定y,你想找到x使得x ^(a / b)= y,其中a和b是整数。该等式表示x ^ a = y ^ b。你可以计算y ^ b,因为你知道这两个数字。你知道吗,所以你可以 - 正如你最初所怀疑的那样 - 使用二进制斩或可能是一些数值更高效的算法,通过简单地猜测x来解决x ^ a = y ^ b,为此猜测计算x ^ a,将其与y ^ b,然后迭代地改进猜测。

示例:假设我们希望通过此方法找到2 ^ 0.878。然后设置a = 439,b = 500,所以我们希望找到2 ^(439/500)。如果我们设置x = 2 ^(439/500),我们有x ^ 500 = 2 ^ 439,所以计算2 ^ 439和(通过二进制斩或其他方式)找到x使得x ^ 500 = 2 ^ 439。

答案 2 :(得分:0)

如评论中所述,不清楚您是否需要对分数幂如何工作的数学描述,或者计算分数幂的算法。

我会假设后者。

对于几乎所有函数(例如y = 2 ^ x),都有一种使用称为泰勒系列http://orientdb.com/docs/last/SQL-Functions.html#traversededge的函数逼近函数的方法。这将任何合理表现的函数近似为多项式,并且可以仅使用乘法,除法,加法和减法来计算多项式(CPU可以直接执行所有这些操作)。如果计算y = 2 ^ x的泰勒级数并插入x = 3.5,则得到11.313 ......

这几乎肯定不是你的计算机实际上是如何进行取幂的。对于不同的输入,有许多算法运行得更快。例如,如果使用泰勒级数计算2 ^ 3.5,则必须查看许多项以便以任何精度计算它。但是,对于x = 0.5,泰勒级数会收敛得比x = 3.5时快得多。因此,一个明显的改进是将2 ^ 3.5计算为2 ^ 3 * 2 ^ 0.5,因为2 ^ 3易于直接计算。现代指数算法将使用许多很多技巧来加速处理 - 但原理仍然大致相同,将取幂函数近似为一些无穷和,并计算所需数量,以获得所需的精度。

答案 3 :(得分:0)

其中大部分归结为能够反转电源操作。

换句话说,基本思想是(例如)N 2 应该基本上是N 1/2 的“相反”,这样如果你做某事像:

M = N 2

L = M 1/2

然后你在L中得到的结果应该与N中的原始值相同(忽略任何舍入等)。

数学上,这意味着N 1/2 sqrt(N)相同,N 1/3 是N的立方根,依此类推

之后的下一步将是N 3/2 。这几乎是一个相同的想法:分母是根,分子是幂,所以N 3/2 是N的立方的平方根(或平方根的立方根) N - 也是一样的。)

使用小数,我们只是以略微不同的形式表达一个分数,所以N 3.14 之类的东西可以被视为N 314/100 - 百分之一N的根升到了力量314。

至于你如何计算这些:有很多不同的方法,很大程度上取决于你喜欢的复杂性(芯片面积,如果你在硬件中实现)和速度之间的折衷。显而易见的方法是使用对数:AB = Log-1(Log(A)*B)

对于更受限制的输入集合,例如只找到N的平方根,您通常可以比非常通用的方法做得更好。例如,binary reducing method非常快 - 用软件实现,它仍然与英特尔FSQRT指令的速度大致相同。

答案 4 :(得分:0)

从其他优秀帖子中获取想法,我想出了自己的实现。答案基于base^(exponent*accuracy) = answer^accuracy的想法。鉴于我们事先知道baseexponentaccuracy变量,我们可以执行搜索(二进制或其他),以便通过查找answer来平衡等式。我们希望方程两边的指数都是整数(否则我们会回到原点1),所以我们可以使accuracy任意大小,然后将其舍入到最接近的整数

我已经有两种方法可以做到这一点。第一种是非常慢,并且通常会产生非常高的数字,这些数字不会与大多数语言一起使用。另一方面,它没有使用日志,并且在概念上更简单。

public double powSimple(double a, double b)
{
    int accuracy = 10;

    bool negExponent = b < 0;
    b = Math.Abs(b);
    bool ansMoreThanA = (a>1 && b>1) || (a<1 && b<1);   // Example 0.5^2=0.25 so answer is lower than A.
    double accuracy2 = 1.0 + 1.0 / accuracy;
    double total = a;
    for (int i = 1; i < accuracy* b; i++) total = total*a;

    double t = a;           
    while (true) {
        double t2 = t;
        for(int i = 1; i < accuracy; i++) t2 = t2 * t; // Not even a binary search. We just hunt forwards by a certain increment
        if((ansMoreThanA && t2 > total) || (!ansMoreThanA && t2 < total)) break;
        if (ansMoreThanA) t *= accuracy2; else t /= accuracy2;
    }
    if (negExponent) t = 1 / t;
    return t;
}

下面这个有一点涉及,因为它使用log()。但它更快,并且不会受到上述超高数字问题的影响。

public double powSimple2(double a, double b)
{
    int accuracy = 1000000;

    bool negExponent= b<0;
    b = Math.Abs(b);
    double accuracy2 = 1.0 + 1.0 / accuracy;
    bool ansMoreThanA = (a>1 && b>1) || (a<1 && b<1);   // Example 0.5^2=0.25 so answer is lower than A.

    double total = Math.Log(a) * accuracy * b;

    double t = a;
    while (true) {
        double t2 = Math.Log(t) * accuracy;
        if ((ansMoreThanA && t2 > total) || (!ansMoreThanA && t2 < total)) break;
        if (ansMoreThanA) t *= accuracy2; else t /= accuracy2;
    }
    if (negExponent) t = 1 / t;
    return t;
}