【问题标题】:self made pow() c++自制 pow() C++
【发布时间】:2012-03-11 04:48:26
【问题描述】:

我正在阅读How can I write a power function myself?,dan04 给出的答案引起了我的注意,主要是因为我不确定 fortran 给出的答案,但我接受并实现了这个:

#include <iostream>
using namespace std;
float pow(float base, float ex){
    // power of 0
    if (ex == 0){
        return 1;
    // negative exponenet
    }else if( ex < 0){
        return 1 / pow(base, -ex);
    // even exponenet
    }else if ((int)ex % 2 == 0){
        float half_pow = pow(base, ex/2);
        return half_pow * half_pow;
    //integer exponenet
    }else{
        return base * pow(base, ex - 1);
    }
}
int main(){
    for (int ii = 0; ii< 10; ii++){\
        cout << "pow(" << ii << ".5) = " << pow(ii, .5) << endl;
        cout << "pow(" << ii << ",2) = " << pow(ii,  2) << endl;
        cout << "pow(" << ii << ",3) = " << pow(ii,  3) << endl;
    }
}

虽然我不确定我是否翻译正确,因为所有以 0.5 作为指数的调用都返回 0。在答案中它指出它可能需要基于 a^b = 2^(b * log2(a)) 的 log2(x),但我是不确定将其放入,因为我不确定将其放入何处,或者我什至在考虑是否正确。

注意:我知道这可能在数学库中定义,但我不需要为几个函数增加整个数学库的所有额外费用。

编辑:有人知道小数指数的浮点实现吗? (我见过双重实现,但那是使用寄存器技巧,我需要浮点,并且添加一个库只是为了做一个技巧我最好只包括数学库)

【问题讨论】:

  • 或者增加一个 FPU 的费用?
  • 您缺少小数指数(偶数指数的代码) - 查看原始链接,我认为您正在从仅支持整数指数的东西中复制(因此测试小数将失败)。
  • 我向你保证,这比 math.h 的战俘要贵。
  • @BrettHale 它是 log2(n) 不是吗?所以对于 pow(base, 1000000) 它的深度约为 20。
  • @andrewcooke - 当然,如果 ((float) ex % 2 == 0) 确实有效。

标签: c++ math


【解决方案1】:

我看过这篇论文here,它描述了如何逼近双精度的指数函数。在对 Wikipedia 进行了一些关于单精度浮点表示的研究之后,我已经制定了等效的算法。他们只实现了 exp 函数,所以我找到了一个 log 的反函数,然后简单地做了

    POW(a, b) = EXP(LOG(a) * b).

编译这个 gcc4.6.2 生成的 pow 函数几乎比标准库的实现(使用 O2 编译)快 4 倍。

注意:EXP 的代码几乎是从我阅读的论文中逐字复制的,LOG 函数是从here 复制的。

以下是相关代码:

    #define EXP_A 184
    #define EXP_C 16249 

    float EXP(float y)
    {
      union
      {
        float d;
        struct
        {
    #ifdef LITTLE_ENDIAN
          short j, i;
    #else
          short i, j;
    #endif
        } n;
      } eco;
      eco.n.i = EXP_A*(y) + (EXP_C);
      eco.n.j = 0;
      return eco.d;
    }

    float LOG(float y)
    {
      int * nTemp = (int*)&y;
      y = (*nTemp) >> 16;
      return (y - EXP_C) / EXP_A;
    }

    float POW(float b, float p)
    {
      return EXP(LOG(b) * p);
    }

您仍然可以在此处进行一些优化,或者这已经足够了。 这是一个粗略的近似值,但如果您对使用双重表示引入的错误感到满意,我想这将是令人满意的。

【讨论】:

    【解决方案2】:

    我认为您正在寻找的算法可能是“nth root”。初始猜测为 1(对于 k == 0):

    #include <iostream>
    using namespace std;
    
    
    float pow(float base, float ex);
    
    float nth_root(float A, int n) {
        const int K = 6;
        float x[K] = {1};
        for (int k = 0; k < K - 1; k++)
            x[k + 1] = (1.0 / n) * ((n - 1) * x[k] + A / pow(x[k], n - 1));
        return x[K-1];
    }
    
    float pow(float base, float ex){
        if (base == 0)
            return 0;
        // power of 0
        if (ex == 0){
            return 1;
        // negative exponenet
        }else if( ex < 0){
            return 1 / pow(base, -ex);
        // fractional exponent
        }else if (ex > 0 && ex < 1){
            return nth_root(base, 1/ex);
        }else if ((int)ex % 2 == 0){
            float half_pow = pow(base, ex/2);
            return half_pow * half_pow;
        //integer exponenet
        }else{
            return base * pow(base, ex - 1);
        }
    }
    int main_pow(int, char **){
        for (int ii = 0; ii< 10; ii++){\
            cout << "pow(" << ii << ", .5) = " << pow(ii, .5) << endl;
            cout << "pow(" << ii << ",  2) = " << pow(ii,  2) << endl;
            cout << "pow(" << ii << ",  3) = " << pow(ii,  3) << endl;
        }
        return 0;
    }
    

    测试:

    pow(0, .5) = 0.03125
    pow(0,  2) = 0
    pow(0,  3) = 0
    pow(1, .5) = 1
    pow(1,  2) = 1
    pow(1,  3) = 1
    pow(2, .5) = 1.41421
    pow(2,  2) = 4
    pow(2,  3) = 8
    pow(3, .5) = 1.73205
    pow(3,  2) = 9
    pow(3,  3) = 27
    pow(4, .5) = 2
    pow(4,  2) = 16
    pow(4,  3) = 64
    pow(5, .5) = 2.23607
    pow(5,  2) = 25
    pow(5,  3) = 125
    pow(6, .5) = 2.44949
    pow(6,  2) = 36
    pow(6,  3) = 216
    pow(7, .5) = 2.64575
    pow(7,  2) = 49
    pow(7,  3) = 343
    pow(8, .5) = 2.82843
    pow(8,  2) = 64
    pow(8,  3) = 512
    pow(9, .5) = 3
    pow(9,  2) = 81
    pow(9,  3) = 729
    

    【讨论】:

    • 很抱歉还有其他项目要处理。当给出 .3 时,这会崩溃
    • 我尝试添加 .3,似乎可行(我使用的是 GCC 4.5)。但请参阅 base == 0... 的编辑
    • @chac:你真的试过1/3吗?因为整数除法是 0。要使其浮动除法,您需要改用1./3.(实际上其中一个点就足够了)。
    • @celtschk:我尝试过使用浮点数,但这种方法存在根本缺陷,在 nth_root(base, 1/ex); 1/ex 执行的整数转换失去精度;
    • 非常感谢。我在 Pebble 开发中使用它
    【解决方案3】:

    我认为您可以尝试使用泰勒级数来解决它, 检查这个。 http://en.wikipedia.org/wiki/Taylor_series

    使用泰勒级数,您可以使用已知结果(例如 3^4)解决任何难以解决的计算,例如 3^3.8。在这种情况下,您有 3^4 = 81 所以

    3^3.8 = 81 + 3.8*3( 3.8 - 4) +..+.. 等等取决于你的 n 有多大,你会得到更接近问题的解决方案。

    【讨论】:

    • 泰勒级数涉及曲线拟合问题,需要知道适当数量的项才能生成准确的值。少,你可以很容易地达到不断增加/减少,多,你的浪费过程。求和的k个数有合理的建议吗?
    • 您可以将系列中最后一次计算的结果与一些标准数字(如 0.0000001)进行比较,因此这是准确性的限制。因此,您必须将其作为总和的迭代,并检查每次迭代的计算,如果它的最终值
    • 泰勒级数仅在一个点上进行近似,因为 (chebyshev, remez) 等极小极大算法超过了一个界限,从而给出了最优的等波纹误差。
    【解决方案4】:

    我和我的朋友在进行 OpenGL 项目时遇到了类似的问题,而 math.h 在某些情况下还不够。我们的教练也遇到了同样的问题,他告诉我们将整数部分和浮动部分分开。例如,如果您要计算 x^11.5,则可以计算 sqrt(x^115, 10),这可能会得到更准确的结果。

    【讨论】:

    • 这需要一个更大的指数才能开始。虽然它可以简化计算浮点幂的过程,但它也会增加在幂函数中进行的递归或循环调用的数量(考虑到除非进行位数学运算,否则它需要某种循环)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-02
    • 1970-01-01
    • 2017-07-14
    相关资源
    最近更新 更多