首页 文章

浮点线性插值

提问于
浏览
21

要给出分数 f 两个变量 ab 之间的线性插值,我目前正在使用此代码:

float lerp(float a, float b, float f) 
{
    return (a * (1.0 - f)) + (b * f);
}

我认为这可能是一种更有效的方法 . 我使用的是没有FPU的微控制器,因此浮点运算是在软件中完成的 . 它们相当快,但它仍然可以添加或增加100个周期 .

有什么建议?

注:为了清楚起见,在上面的代码中,我们可以省略将 1.0 指定为显式浮点文字 .

5 回答

  • 1

    如果您希望最终结果为整数,那么对输入使用整数可能会更快 .

    int lerp_int(int a, int b, float f)
    {
        //float diff = (float)(b-a);
        //float frac = f*diff;
        //return a + (int)frac;
        return a + (int)(f * (float)(b-a));
    }
    

    这会做两次演员和一次浮动 . 如果演员阵容比平台上的浮点数加/减快,并且整数答案对您有用,这可能是一个合理的选择 .

  • 20

    假设浮点数学是可用的,OP的算法是一个很好的算法,并且由于精度损失,当 ab 在幅度上有显着差异时,总是优于备选 a + f * (b - a) .

    例如:

    // OP's algorithm
    float lint1 (float a, float b, float f) {
        return (a * (1.0f - f)) + (b * f);
    }
    
    // Algebraically simplified algorithm
    float lint2 (float a, float b, float f) {
        return a + f * (b - a);
    }
    

    在该示例中,假设32位浮点数 lint1(1.0e20, 1.0, 1.0) 将正确返回1.0,而 lint2 将错误地返回0.0 .

    当操作数的大小差异很大时,大多数精度损失在加法和减法运算符中 . 在上面的例子中,罪魁祸首是 b - a 中的减法,以及 a + f * (b - a) 中的加法 . 由于组件在加法之前完全相乘,因此OP的算法不会受此影响 .


    对于a = 1e20,b = 1的情况,这是不同结果的示例 . 测试程序:

    #include <stdio.h>
    #include <math.h>
    
    float lint1 (float a, float b, float f) {
        return (a * (1.0f - f)) + (b * f);
    }
    
    float lint2 (float a, float b, float f) {
        return a + f * (b - a);
    }
    
    int main () {
        const float a = 1.0e20;
        const float b = 1.0;
        int n;
        for (n = 0; n <= 1024; ++ n) {
            float f = (float)n / 1024.0f;
            float p1 = lint1(a, b, f);
            float p2 = lint2(a, b, f);
            if (p1 != p2) {
                printf("%i %.6f %f %f %.6e\n", n, f, p1, p2, p2 - p1);
            }
        }
        return 0;
    }
    

    输出,略微调整格式:

    f            lint1               lint2             lint2-lint1
    0.828125  17187500894208393216  17187499794696765440  -1.099512e+12
    0.890625  10937500768952909824  10937499669441282048  -1.099512e+12
    0.914062   8593750447104196608   8593749897348382720  -5.497558e+11
    0.945312   5468750384476454912   5468749834720641024  -5.497558e+11
    0.957031   4296875223552098304   4296874948674191360  -2.748779e+11
    0.972656   2734375192238227456   2734374917360320512  -2.748779e+11
    0.978516   2148437611776049152   2148437474337095680  -1.374390e+11
    0.986328   1367187596119113728   1367187458680160256  -1.374390e+11
    0.989258   1074218805888024576   1074218737168547840  -6.871948e+10
    0.993164    683593798059556864    683593729340080128  -6.871948e+10
    1.000000                     1                     0  -1.000000e+00
    
  • 7

    如果您使用的是没有FPU的微控制器,那么浮点将非常昂贵 . 对于浮点运算,可能容易慢20倍 . 最快的解决方案是使用整数进行所有数学运算 .

    固定二进制点(http://blog.credland.net/2013/09/binary-fixed-point-explanation.html?q=fixed+binary+point)之后的位数是:XY_TABLE_FRAC_BITS .

    这是我使用的一个功能:

    inline uint16_t unsignedInterpolate(uint16_t a, uint16_t b, uint16_t position) {
        uint32_t r1;
        uint16_t r2;
    
        /* 
         * Only one multiply, and one divide/shift right.  Shame about having to
         * cast to long int and back again.
         */
    
        r1 = (uint32_t) position * (b-a);
        r2 = (r1 >> XY_TABLE_FRAC_BITS) + a;
        return r2;    
    }
    

    内联功能应该是约 . 10-20个周期 .

    如果你有一个32位微控制器,你将能够使用更大的整数,在不影响性能的情况下获得更大的数字或更高的精度 . 该功能用于16位系统 .

  • 5

    如果你最好不要使用浮点数,而是使用fixed-point arithmetic .

  • 4

    不考虑精度的差异,该表达式相当于

    float lerp(float a, float b, float f)
    {
        return a + f * (b - a);
    }
    

    这是2次加法/减法和1次乘法,而不是2次加法/减法和2次乘法 .

相关问题