浮点线性插值

Tho*_*s O 24 c embedded algorithm interpolation linear-interpolation

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

float lerp(float a, float b, float f) 
{
    return (a * (1.0 - f)) + (b * f);
}
Run Code Online (Sandbox Code Playgroud)

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

有什么建议?

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

aio*_*obe 25

忽略精度差异,表达式相当于

float lerp(float a, float b, float f)
{
    return a + f * (b - a);
}
Run Code Online (Sandbox Code Playgroud)

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

  • 当a和b在指数上显着不同时,由于精度损失,这不是等效算法.OP的算法总是更好的选择.例如,对于`lerp(-16.0e30,16.0,1.0)`,本答案中的算法将返回0,而不是OP算法产生的正确结果16.当"a"明显大于"f*(b - a)"时,精度损失发生在加法运算符中,而"(b - a)"中的减法运算符则出现精度损失. (28认同)
  • 只需将浮点数视为定点尾数和指数(它比这更复杂,但以这种方式查看它们足以*发现*许多*麻烦区域).因此,如果超过尾数的精度,您将开始丢失信息.在概念上类似于我们不能,例如,代表1,230,000十进制,只有两位有效数字(1.2*10 ^ 6是我们最接近的),所以如果你做1,200,000 + 30,000但你只有两位有效数字你的处置,你失去了30,000. (3认同)
  • @coredump很抱歉2年前没有注意到你的评论(嘿......).特别是,如果`f*(b - a)`的幅度与该算法中的'a'显着不同,那么OP将更加精确,然后加法分崩离析.这是你遇到麻烦的加法/减法.如果`f`相对于`1.0f`来说太大,那么即使OP也会失败,因为对于非常大的`f`,`1.0f-f`可能变得等于`-f`.因此,如果你正在使用`f`的巨大值,你需要仔细考虑一下数学.问题是你遇到了像"1.0 + 1.0e800 == 1.0e800"这样的问题. (2认同)
  • 虽然它通常可能是更好的选择,但 OP 的算法并不总是更好的选择。考虑当“a == b”时的情况:该算法将始终返回正确的答案,但根据“t”的值,OP的算法可能会在加法的左侧和右侧都失去精度,并且它不会累加到初始值。 (2认同)

小智 8

如果您使用的是没有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;    
}
Run Code Online (Sandbox Code Playgroud)

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

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


Jas*_*n C 6

假定浮点数学是可行的,OP的算法是一个很好的算法,并且a + f * (b - a)由于精度损失a而且b在幅度上有很大差异,因此总是优于替代算法.

例如:

// 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);
}
Run Code Online (Sandbox Code Playgroud)

在该示例中,假设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;
}
Run Code Online (Sandbox Code Playgroud)

输出,略微调整格式:

    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

  • 有趣的是,OP的版本并不总是优越的.我以为它被这个例子咬了一下:`lerp(0.45,0.45,0.81965185546875)`.它显然应该给出0.45,但至少对于双精度我得到0.45000000000000007而显然a +(ba)*f版本给出a = = b.我很想看到一个算法,它具有`lerp(a,b,f)`返回`a`如果`f == 0`,`b`如果`f == 1`,并保留在范围[`a`,`b`]为[0,1]中的`f`. (3认同)
  • 首先,您需要“if a == b -&gt; return a”的情况。但是,精确的 0.45 不可能用双精度或浮点精度表示,因为它不是 2 的精确幂。在您的示例中,所有参数 `a、b、f` 在函数调用内部时都存储为双精度 - 返回 `a ` 永远不会准确返回 0.45。(当然,对于像 C 这样的显式类型语言) (3认同)

0kc*_*ats 6

值得注意的是,标准线性插值公式 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 < t < 1 时。

当我需要结果表现良好并准确到达端点时,此函数在支持 IEEE754 浮点的处理器上对我有用(我以双精度使用它,但 float 也应该工作):

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);
}
Run Code Online (Sandbox Code Playgroud)