Vorausgesetzt, Fließkomma-Mathematik ist verfügbar, der OP-Algorithmus ist ein guter und ist immer besser als die Alternative a + f * (b - a)
aufgrund Präzisionsverlust, wenn und b
deutlich in der Größenordnung unterscheiden.
Zum Beispiel:
// 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 diesem Beispiel 32-Bit vorausgesetzt schwimmt lint1(1.0e20, 1.0, 1.0)
korrekt 1,0 zurück, während lint2
falsch 0,0 zurück.
Die Mehrheit der Genauigkeitsverlust ist in den Additions- und Subtraktionsoperatoren, wenn die Operanden signifikant in der Größenordnung unterscheiden. Im obigen Fall sind die Täter die Subtraktion in b - a
und die Addition in a + f * (b - a)
. Der Algorithmus des OP leidet darunter nicht, da die Komponenten vor der Addition vollständig multipliziert werden.
Für die a = 1E20, b = 1 Fall, hier ist ein Beispiel für unterschiedliche Ergebnisse. Prüfprogramm:
#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;
}
Ausgang, leicht für die Formatierung eingestellt:
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
Dies ist nicht ein äquivalenter Algorithmus durch Präzisionsverlust, wenn a und b deutlich in Exponenten unterscheiden. Der Algorithmus des OP ist immer die bessere Wahl. Zum Beispiel gibt der Algorithmus in dieser Antwort für 'lerp (-16.0e30, 16.0, 1.0)' 0 zurück, im Gegensatz zum korrekten Ergebnis, 16, welches der Algorithmus des OP erzeugt. Der Genauigkeitsverlust tritt im Additionsoperator auf, wenn "a" wesentlich größer ist als "f * (b - a)", und im Subtraktionsoperator in "(b - a)". –
Der ursprüngliche Algorithmus ist auch nicht sehr leistungsmäßig verlustbehaftet: FP-Multiplikation ist viel schneller als FP-Addition, und wenn 'f' garantiert zwischen 0 und 1 liegt, sind bestimmte Optimierungen zu '(1-f) 'möglich . – Sneftel
@Sneftel: Können Sie die Optimierungen für '1 - f' näher erläutern? Ich bin zufällig in dieser Situation und bin neugierig: D –