Suponiendo que la matemática de coma flotante está disponible, el algoritmo de OP es bueno y siempre es superior a la alternativa a + f * (b - a)
debido a la pérdida de precisión cuando a
y b
difieren significativamente en magnitud.
Por ejemplo:
// 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);
}
En ese ejemplo, suponiendo 32 bits flota lint1(1.0e20, 1.0, 1.0)
devolverá correctamente 1,0, mientras que lint2
se incorrectamente volver 0.0.
La mayoría de la pérdida de precisión se produce en los operadores de suma y resta cuando los operandos difieren significativamente en magnitud. En el caso anterior, los culpables son la resta en b - a
, y la adición en a + f * (b - a)
. El algoritmo de OP no sufre de esto debido a que los componentes se multiplican por completo antes de la suma.
Para el a = 1e20, b = 1 caso, aquí es un ejemplo de resultados diferentes. Programa de prueba:
#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;
}
de salida, ligeramente ajustado para el formato:
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
Esto no es un algoritmo equivalente debido a la pérdida de precisión cuando A y B difieren significativamente en exponentes. El algoritmo de OP es siempre la mejor opción. Por ejemplo, el algoritmo en esta respuesta, para 'lerp (-16.0e30, 16.0, 1.0)' devolverá 0, en oposición al resultado correcto, 16, que produce el algoritmo del OP. La pérdida de precisión ocurre en el operador de suma, cuando 'a' es significativamente más grande que' f * (b - a) ', y en el operador de resta en' (b - a) '. –
El algoritmo original tampoco es una gran pérdida de rendimiento: la multiplicación FP es mucho más rápida que la FP, y si se garantiza que 'f' está entre 0 y 1, ciertas optimizaciones a' (1-f) 'son posibles . – Sneftel
@Sneftel: ¿Puede explicar las optimizaciones para '1 - f'? Resulta que estoy en esa situación y tengo curiosidad: D –