2010-12-04 9 views
19

이 두 변수 a 및 일부 f 주어진 b 사이에 선형 보간을 수행하려면, 나는 현재이 코드를 사용하고 있습니다 :부동 소수점 선형 보간

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

내가 그 일을 좀 더 효율적인 방법이 거기에 아마 생각합니다. FPU가없는 마이크로 컨트롤러를 사용하기 때문에 부동 소수점 연산은 소프트웨어로 수행됩니다. 그것들은 비교적 빠르지 만, 덧셈이나 곱셈은 여전히 ​​100 사이클과 같습니다.

제안 사항?

n.b. 위의 코드의 식에서 명확성을 위해 명시 적 부동 소수점 리터럴으로 1.0을 생략 할 수 있습니다. 정밀도의 차이로부터 기울이지

답변

16

, 즉 식 2 가산/감산 1 곱셈 대신 2 부가/감산과 2 승산있어

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

동일하다.

+20

이 때문에 정밀도가 손실 등가 알고리즘 아니다. OP의 알고리즘은 항상 더 나은 선택입니다. 예를 들어,이 대답의 알고리즘은'lerp (-16.0e30, 16.0, 1.0)'에 대해 OP의 알고리즘이 생성하는 올바른 결과 인 16 대신에 0을 반환합니다. 정밀도 손실은 'a'가'f * (b-a)'보다 상당히 크고'(b-a)'의 빼기 연산자에서 더하기 연산자에서 발생합니다. –

+0

원래의 알고리즘은 성능 측면에서도 큰 손실이 없습니다. FP 곱셈은 FP 덧셈보다 훨씬 빠르며, f가 0과 1 사이의 값을 갖게된다면, (1-f)에 대한 최적화가 가능합니다 . – Sneftel

+0

@Sneftel :''1-f''에 대한 최적화에 대해 자세히 설명해 주시겠습니까? 나는 그런 상황에 처해 있으며 궁금하다. D –

4

부동 소수점 연산이없는 마이크로 컨트롤러를 코딩하는 경우 부동 소수점 숫자를 전혀 사용하지 않는 것이 좋으며 fixed-point arithmetic을 대신 사용하는 것이 좋습니다.

+0

고정 소수점으로 마이그레이션 할 계획이지만 부동 소수점은 이미 매우 빠릅니다. –

1

최종 결과를 정수로 나타내려면 정수에 정수를 사용하는 것이 더 빠릅니다.

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

이것은 두 개의 캐스트와 하나의 부동 소수점 곱하기입니다. 캐스트가 플랫폼의 부동 소수점 더하기/빼기보다 빠르며 정수형 대답이 유용 할 경우 합리적인 대안이 될 수 있습니다.

6

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 비트 시스템에서 사용되었습니다.

+0

나는 웹 사이트를 읽었지 만, 어느 위치에 있어야하는지에 대해서는 조금 혼란 스럽다. 이 값은 0 ~ 0xFFFF입니까? 또는 0 ~ 0xFFFE? 또한 XY_TABLE_FRAC_BITS은 무엇입니까? 8? – jjxtra

4

부동 소수점 연산을 가정 할 때 OP의 알고리즘은 우수하며 ab의 크기가 크게 다를 때 정밀도 손실로 인해 항상 a + f * (b - a)보다 우수합니다. 예를 들어

: lint2 잘못 0.0 반환 반면 그 예에서

// 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 반환 수레.

피연산자의 수가 크게 다른 경우 정밀도 손실의 대부분은 더하기 및 빼기 연산자에 있습니다. 위의 경우에 범인은 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; 
} 

출력 약간 조정 서식 : A와 B는 상당히 다를 때 지수

 
    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 
+2

흥미롭게도 OP의 버전이 항상 우수한 것은 아닙니다. 나는이 예제에 물린 것으로 생각했다 :'lerp (0.45, 0.45, 0.81965185546875)'. 분명히 0.45를 주어야하지만, 적어도 double precision의 경우 0.45000000000000007이됩니다. 반면 a + (b-a) * f 버전은 a == b 일 때 분명합니다. 'lerp (a, b, f)'가'f == 0'일 경우'a'를,'f == 1' 일 경우'b'를, 그리고 머무르는 속성을 가진 알고리즘을보고 싶습니다. [0,1]의'f'에 대한 ['a','b'] 범위. – Ben

+0

먼저, a == b -> a를 반환하는 경우 'a'가 필요합니다. 그러나 정확히 0입니다.45는 2의 정확한 거듭 제곱이 아니므로 이중 또는 부동 소수점 정밀도로 표현할 수 없습니다. 예제에서 모든 매개 변수 'a, b, f'는 함수 호출 내부에서 double로 저장됩니다. 절대로 정확하게 0.45를 반환하지 않습니다. (물론 C와 같이 명시 적으로 입력 된 언어의 경우) –