Ich habe einige Tests mit C++
hypot()
und Java
Math.hypot
durchgeführt. Beide scheinen deutlich langsamer zu sein als sqrt(a*a + b*b)
. Liegt das an einer besseren Präzision? Welche Methode zur Berechnung einer Hypotenuse hypot
Funktion verwendet? Überraschenderweise konnte ich in der Dokumentation keine Hinweise auf eine schlechte Leistung finden.Warum Hypot() Funktion ist so langsam?
Antwort
Es ist keine einfache sqrt-Funktion. Sie sollten diesen Link überprüfen für die Implementierung des Algorithmus: http://www.koders.com/c/fid7D3C8841ADC384A5F8DE0D081C88331E3909BF3A.aspx
Es hat while-Schleife für die Konvergenz zu überprüfen
/* Slower but safer algorithm due to Moler and Morrison. Never
produces any intermediate result greater than roughly the
larger of X and Y. Should converge to machine-precision
accuracy in 3 iterations. */
double r = ratio*ratio, t, s, p = abig, q = asmall;
do {
t = 4. + r;
if (t == 4.)
break;
s = r/t;
p += 2. * s * p;
q *= s;
r = (q/p) * (q/p);
} while (1);
EDIT (Update von JM):
Here ist die ursprüngliche Moler-Morrison Papier und here ist eine nette Folge wegen Dubrulle.
Wäre nett zu geben Beispiele, bei denen diese Funktion besser ist als der 'sqrt'-Ansatz. – schnaader
@schnaader - lesen Sie die verknüpfte Seite, der Grund ist ganz oben (kurze Version, die naive Methode kann überlaufen, wenn es nicht sollte) – Nir
Dies ist wichtig für sehr große Werte von x und y. Wenn beispielsweise x und y so sind, dass x^2 + y^2 größer als MAX_DOUBLE ist, schlägt die Version sqrt (x^2 + y^2) fehl. Da diese Methode jedoch nie einen Wert erzeugt, der viel größer als x oder y ist, sollte sie in diesen Situationen sicher sein. – pfhayes
Hier ist eine schnellere Implementierung, die Ergebnisse sind auch näher an java.lang.Math.hypot: (NB: für Delorie Implementierung benötigen Umgang mit NaN hinzuzufügen und + -Infinity Eingänge)
private static final double TWO_POW_450 = Double.longBitsToDouble(0x5C10000000000000L);
private static final double TWO_POW_N450 = Double.longBitsToDouble(0x23D0000000000000L);
private static final double TWO_POW_750 = Double.longBitsToDouble(0x6ED0000000000000L);
private static final double TWO_POW_N750 = Double.longBitsToDouble(0x1110000000000000L);
public static double hypot(double x, double y) {
x = Math.abs(x);
y = Math.abs(y);
if (y < x) {
double a = x;
x = y;
y = a;
} else if (!(y >= x)) { // Testing if we have some NaN.
if ((x == Double.POSITIVE_INFINITY) || (y == Double.POSITIVE_INFINITY)) {
return Double.POSITIVE_INFINITY;
} else {
return Double.NaN;
}
}
if (y-x == y) { // x too small to substract from y
return y;
} else {
double factor;
if (x > TWO_POW_450) { // 2^450 < x < y
x *= TWO_POW_N750;
y *= TWO_POW_N750;
factor = TWO_POW_750;
} else if (y < TWO_POW_N450) { // x < y < 2^-450
x *= TWO_POW_750;
y *= TWO_POW_750;
factor = TWO_POW_N750;
} else {
factor = 1.0;
}
return factor * Math.sqrt(x*x+y*y);
}
}
schlägt vor, dass die schnellere Implementierung mit sqrt() quadratisch vs. kubisch für Moler & Morrison ist, mit etwa den gleichen Überlauf-Eigenschaften.
Ich fand Math.hypot() abnormal langsam. Ich fand heraus, dass ich eine schnelle Java-Version mit demselben Algorithmus codieren konnte, der identische Ergebnisse liefert. Dies kann verwendet werden, um es zu ersetzen.
/**
* <b>hypot</b>
* @param x
* @param y
* @return sqrt(x*x +y*y) without intermediate overflow or underflow.
* @Note {@link Math#hypot} is unnecessarily slow. This returns the identical result to
* Math.hypot with reasonable run times (~40 nsec vs. 800 nsec).
* <p>The logic for computing z is copied from "Freely Distributable Math Library"
* fdlibm's e_hypot.c. This minimizes rounding error to provide 1 ulb accuracy.
*/
public static double hypot(double x, double y) {
if (Double.isInfinite(x) || Double.isInfinite(y)) return Double.POSITIVE_INFINITY;
if (Double.isNaN(x) || Double.isNaN(y)) return Double.NaN;
x = Math.abs(x);
y = Math.abs(y);
if (x < y) {
double d = x;
x = y;
y = d;
}
int xi = Math.getExponent(x);
int yi = Math.getExponent(y);
if (xi > yi + 27) return x;
int bias = 0;
if (xi > 510 || xi < -511) {
bias = xi;
x = Math.scalb(x, -bias);
y = Math.scalb(y, -bias);
}
// translated from "Freely Distributable Math Library" e_hypot.c to minimize rounding errors
double z = 0;
if (x > 2*y) {
double x1 = Double.longBitsToDouble(Double.doubleToLongBits(x) & 0xffffffff00000000L);
double x2 = x - x1;
z = Math.sqrt(x1*x1 + (y*y + x2*(x+x1)));
} else {
double t = 2 * x;
double t1 = Double.longBitsToDouble(Double.doubleToLongBits(t) & 0xffffffff00000000L);
double t2 = t - t1;
double y1 = Double.longBitsToDouble(Double.doubleToLongBits(y) & 0xffffffff00000000L);
double y2 = y - y1;
double x_y = x - y;
z = Math.sqrt(t1*y1 + (x_y*x_y + (t1*y2 + t2*y))); // Note: 2*x*y <= x*x + y*y
}
if (bias == 0) {
return z;
} else {
return Math.scalb(z, bias);
}
}
Was ist "deutlich langsamer"? Können Sie diesen Wert quantifizieren? Hast du einen Profiler benutzt? Wie oft haben Sie die Tests durchgeführt? Kannst du dein Experiment (DOE) beschreiben? – linuxuser27
In Java war es um einen Faktor von ~ 7 in C++ ~ 10 langsamer. Das haben wir selbständig mit meinem Kollegen beim Testen eines der Programmierprobleme für den kommenden Programmierwettbewerb an einer Universität gefunden. – Leonid
@ linuxuser27: und die zwei Leute, die seinen Kommentar upvoted, überprüfen Ravi Gummadi +9 upvoted Antwort für Aufklärung. – SyntaxT3rr0r