web-dev-qa-db-ja.com

浮動小数点線形補間

分数aを指定して、2つの変数bfの間で線形補間を行うには、現在次のコードを使用しています。

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

おそらくもっと効率的な方法があると思います。 FPUなしのマイクロコントローラーを使用しているため、浮動小数点演算はソフトウェアで行われます。それらはかなり高速ですが、それでも加算または乗算する100サイクルのようなものです。

助言がありますか?

n.b。上記のコードの方程式を明確にするために、1.0を明示的な浮動小数点リテラルとして指定することを省略できます。

31
Thomas O

精度の違いを無視すると、その式は

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

これは、2つの加算/減算と2つの乗算ではなく、2つの加算/減算と1つの乗算です。

24
aioobe

FPUのないマイクロコントローラーを使用している場合、浮動小数点は非常に高価になります。浮動小数点演算では、簡単に20倍遅くなる可能性があります。最速の解決策は、整数を使用してすべての計算を行うことです。

固定2進小数点( 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ビットシステムで使用されました。

8
Jim Credland

浮動小数点演算が利用可能であると想定すると、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;
}
_

フォーマット用に少し調整された出力:

[。 [.____。 +11 
 0.986328 1367187596119113728 1367187458680160256 -1.374390e + 11 
 0.989258 1074218805888024576 1074218737168547840 -6.871948e + 10 
 0.993164 683593798059556864 683593729340080128 -6.871948e + 10 [._ 1 1.000000e + 00 
6
Jason C

浮動小数点演算のないマイクロコントローラーをコーディングする場合は、浮動小数点数をまったく使用せず、代わりに 固定小数点演算 を使用することをお勧めします。

3
Gareth Rees

それは以前にグーグルによって書かれましたが、それはシンプルで、あなたはあなた自身のものを書くことができますが、なぜですか?.

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

この関数は、開始値と終了値の比率を表す小数部を使用して、開始値と終了値を線形補間した結果を返します。

0
steve moretz

最終結果を整数にしたい場合は、入力にも整数を使用した方が速い場合があります。

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

これは2つのキャストと1つの浮動小数点乗算を行います。キャストがプラットフォームの浮動小数点加算/減算よりも高速で、整数の答えが役立つ場合は、これが妥当な代替手段になる可能性があります。

0
mtrw