浮点线性插值

时间:2010-12-04 12:41:29

标签: c algorithm embedded interpolation linear-interpolation

要在给定分数a的两个变量bf之间进行线性插值,我目前正在使用此代码:

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

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

有什么建议吗?

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

8 个答案:

答案 0 :(得分:21)

忽略精度差异,该表达式等同于

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

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

答案 1 :(得分: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位系统。

答案 2 :(得分:5)

假定浮点数学是可用的,OP的算法是一个很好的算法,并且由于a + f * (b - a)ab// 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); } 显着不同时精度损失,因此总是优于备选lint1(1.0e20, 1.0, 1.0)

例如:

lint2

在该示例中,假设32位浮点数b - a将正确返回1.0,而a + f * (b - a)将错误地返回0.0。

当操作数的大小差别很大时,大多数精度损失都在加法和减法运算符中。在上面的例子中,罪犯是#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; } 中的减法,{{1}}中的加法。由于组件在添加之前完全相乘,因此OP的算法不会受此影响。


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

{{1}}

输出,略微调整格式:

    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

答案 3 :(得分:3)

如果您正在为没有浮点运算的微控制器编码,那么最好不要使用浮点数,而是使用fixed-point arithmetic代替。

答案 4 :(得分:2)

自C ++ 20起,您可以使用std::lerp(),这可能是您目标的最佳实现。

答案 5 :(得分:1)

值得注意的是,标准线性插值公式f1(t)= a + t(ba),f2(t)= b-(ba)(1-t)和f3(t)= a使用浮点算法时,(1-t)+ bt不保证是单调的。 特别是,如果a!= b,则不能保证f1(1.0)== b或f2(0.0)== a,而对于a == b,则不能保证f3(t)等于a ,当0

当我需要结果为单调时,此功能对支持IEEE754浮点的处理器有效(我以双精度使用它,但浮点也应工作):

double lerp(double a, double b, double t) 
{
    if (t <= 0.5)
        return a+(b-a)*t;
    else
        return b-(b-a)*(1.0-t);
}

答案 6 :(得分:0)

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

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));
}

这会使两个演员和一个浮子相乘。如果演员阵容比平台上的浮点数加/减快,并且整数答案对你有用,这可能是一个合理的选择。

答案 7 :(得分:-1)

它是由Google编写的,但是它很简单,您可以自己编写,但是为什么呢?什么时候出现?

new FloatEvaluator().evaluate(fraction, startValue, endValue)

此函数返回线性插值起始值和终止值的结果,小数表示起始值和终止值之间的比例。