浮点线性插值

发布于 2024-10-05 20:18:38 字数 367 浏览 4 评论 0原文

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

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

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

有什么建议吗?

注意,为了使上面代码中的方程更加清晰,我们可以省略将 1.0 指定为显式浮点文字。

To do a linear interpolation between two variables a and b given a fraction f, I'm currently using this code:

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.

Any suggestions?

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

枫以 2024-10-12 20:18:38

正如 Jason C 在评论中指出的那样,您发布的版本很可能是最佳选择,因为它在边缘情况附近具有卓越的精度:

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

如果我们暂时忽略精度,我们可以将表达式简化如下

: ;  a(1 − f) × (ba)
 = a - af + bf
 = a + f(ba)

这意味着我们可以这样写:

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

在这个版本中,我们去掉了一个乘法,但失去了一些精度。

As Jason C points out in the comments, the version you posted is most likely the best choice, due to its superior precision near the edge cases:

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

If we disregard from precision for a while, we can simplify the expression as follows:

    a(1 − f) × (ba)
 = aaf + bf
 = a + f(ba)

Which means we could write it like this:

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

In this version we've gotten rid of one multiplication, but lost some precision.

二货你真萌 2024-10-12 20:18:38

假设浮点数学可用,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);
}

在该示例中,假设 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

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 a and b significantly differ in magnitude.

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 lint2 will incorrectly return 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.


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

#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
听风吹 2024-10-12 20:18:38

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

固定二进制小数点后的位数 (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位系统上使用。

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.

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.

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.

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.

我为君王 2024-10-12 20:18:38

值得注意的是,标准线性插值公式 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 浮点的处理器上对我有用(我以双精度使用它,但浮点也应该工作):

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

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 well-behaved when using floating point arithmetic.
Namely, 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.

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

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);
}
梦幻之岛 2024-10-12 20:18:38

如果您正在为没有浮点运算的微控制器进行编码,那么最好根本不使用浮点数,而使用 定点算术 代替。

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 arithmetic instead.

陌伤浅笑 2024-10-12 20:18:38

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

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

貪欢 2024-10-12 20:18:38

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

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

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文