C语言 浮点线性插值

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4353525/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 07:17:54  来源:igfitidea点击:

Floating point linear interpolation

calgorithmembeddedinterpolationlinear-interpolation

提问by Thomas O

To do a linear interpolation between two variables aand bgiven a fraction f, I'm currently using this code:

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

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

I think there's probably a more efficient way of doing it. I'm using a microcontroller without an FPU, so floating point operations are done in software. They are reasonably fast, but it's still something like 100 cycles to add or multiply.

我认为可能有一种更有效的方法。我使用的是没有 FPU 的微控制器,所以浮点运算是在软件中完成的。它们相当快,但仍然需要大约 100 个周期才能进行加法或乘法运算。

Any suggestions?

有什么建议?

n.b. for the sake of clarity in the equation in the code above, we can omit specifying 1.0as an explicit floating-point literal.

nb 为了上面代码中等式的清晰起见,我们可以省略指定1.0为显式浮点文字。

采纳答案by aioobe

Disregarding differences in precision, that expression is equivalent to

不考虑精度差异,该表达式等效于

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

That's 2 additions/subtractions and 1 multiplication instead of 2 addition/subtractions and 2 multiplications.

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

回答by Jim Credland

If you are on a micro-controller without an FPU then floating point is going to be very expensive. Could easily be twenty times slower for a floating point operation. The fastest solution is to just do all the math using integers.

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

The number of places after the fixed binary point (http://blog.credland.net/2013/09/binary-fixed-point-explanation.html?q=fixed+binary+point) is: XY_TABLE_FRAC_BITS.

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

Here's a function I use:

这是我使用的一个函数:

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

With the function inlined it should be approx. 10-20 cycles.

内联函数后,它应该是大约。10-20 个循环。

If you've got a 32-bit micro-controller you'll be able to use bigger integers and get larger numbers or more accuracy without compromising performance. This function was used on a 16-bit system.

如果您有一个 32 位微控制器,您将能够使用更大的整数并获得更大的数字或更高的精度,而不会影响性能。该函数用于 16 位系统。

回答by Jason C

Presuming floating-point math is available, the OP's algorithm is a good one and is always superior to the alternative a + f * (b - a)due to precision loss when aand bsignificantly differ in magnitude.

假设浮点数学是可用的,OP 的算法是一个很好的算法,并且a + f * (b - a)由于精度损失,当ab幅度明显不同时,它总是优于替代方案。

For example:

例如:

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

In that example, presuming 32-bit floats lint1(1.0e20, 1.0, 1.0)will correctly return 1.0, whereas lint2will incorrectly return 0.0.

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

The majority of precision loss is in the addition and subtraction operators when the operands differ significantly in magnitude. In the above case, the culprits are the subtraction in b - a, and the addition in a + f * (b - a). The OP's algorithm does not suffer from this due to the components being completely multiplied before addition.

当操作数的大小差异很大时,大部分精度损失发生在加法和减法运算符中。在上述情况下,罪魁祸首是 中的减法b - a和 中的加法a + f * (b - a)。由于组件在添加之前完全相乘,因此 OP 的算法不会受到此影响。



For the a=1e20, b=1case, here is an example of differing results. Test program:

对于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;
}

Output, slightly adjusted for formatting:

输出,格式略有调整:

    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

回答by Gareth Rees

If you're coding for a microcontroller without floating-point operations, then it's better not to use floating-point numbers at all, and to use fixed-point arithmeticinstead.

如果您正在为没有浮点运算的微控制器编码,那么最好不要使用浮点数,而是使用定点算法

回答by tambre

Since C++20 you can use std::lerp(), which is likely to be the best possible implementation for your target.

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

回答by 0kcats

It is worth to note, that the standard linear interpolation formulas f1(t)=a+t(b-a), f2(t)=b-(b-a)(1-t), and f3(t)=a(1-t)+bt do not guarantee to be monotonic when using floating point arithmetic. Especially, if a != b, it is not guaranteed that the f1(1.0) == b or that f2(0.0) == a, while for a == b, f3(t) is not guaranteed to be equal to a, when 0 < t < 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 < t < 1。

This function has worked for me on processors that support IEEE754 floating point when I need the results to be monotonic (I use it with double precision, but float should work as well):

当我需要结果是单调的时,这个函数在支持 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);
}

回答by mtrw

If you want to the final result to be an integer, it might be faster to use integers for the input as well.

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

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

This does two casts and one float multiply. If a cast is faster than a float add/subtract on your platform, and if an integer answer is useful to you, this might be a reasonable alternative.

这会进行两次转换和一次浮点乘法。如果在您的平台上转换比浮点加/减快,并且整数答案对您有用,那么这可能是一个合理的选择。