java.lang.StrictMath
Enthält natürlich zusätzliche Funktionen (Hyperbel usw.), die java.lang.Math
dies nicht tun, aber gibt es einen Unterschied in den Funktionen, die in beiden Bibliotheken zu finden sind?
74
java.lang.StrictMath
Enthält natürlich zusätzliche Funktionen (Hyperbel usw.), die java.lang.Math
dies nicht tun, aber gibt es einen Unterschied in den Funktionen, die in beiden Bibliotheken zu finden sind?
Antworten:
Das Javadoc für die
Math
Klasse enthält einige Informationen zu den Unterschieden zwischen den beiden Klassen:Daher
Math
legt die Klasse einige Regeln fest, wie bestimmte Operationen ausgeführt werden sollen, verlangt jedoch nicht, dass in allen Implementierungen der Bibliotheken genau dieselben Ergebnisse zurückgegeben werden.Dies ermöglicht, dass bestimmte Implementierungen der Bibliotheken ähnliche, aber nicht genau das gleiche Ergebnis zurückgeben, wenn beispielsweise die
Math.cos
Klasse aufgerufen wird. Dies würde plattformspezifische Implementierungen ermöglichen (z. B. die Verwendung von x86-Gleitkomma und beispielsweise SPARC-Gleitkomma), die möglicherweise unterschiedliche Ergebnisse liefern.( Einige Beispiele für plattformspezifische Implementierungen finden Sie im Abschnitt Software-Implementierungen des Sinus- Artikels in Wikipedia.)
Bei müssen die
StrictMath
von verschiedenen Implementierungen zurückgegebenen Ergebnisse jedoch dasselbe Ergebnis zurückgeben. Dies wäre in Fällen wünschenswert, in denen die Reproduzierbarkeit der Ergebnisse auf verschiedenen Plattformen erforderlich ist.quelle
Math
Klasse aufgeführten Gründen - um die für die jeweilige Plattform verfügbaren nativen Methoden zu nutzen, die (in vielen Fällen wahrscheinlich) schneller sind als die Verwendung einer softwarebasierten Lösung, die dies garantiert Geben Sie auf allen Plattformen genau die gleiche Antwort.Math
Klassenspezifikation die Verwendung plattformspezifischer Algorithmen, die nicht unbedingt das gleiche Ergebnis wie andere Plattformen liefern.@ntoskrnl Als jemand, der mit JVM-Interna arbeitet, möchte ich Ihre Meinung unterstützen, dass "Intrinsics sich nicht unbedingt so verhalten wie StrictMath-Methoden". Um dies herauszufinden (oder zu beweisen), können wir einfach einen einfachen Test schreiben.
Wenn wir
Math.pow
zum Beispiel den Java-Code für java.lang.Math.pow (double a, double b) untersuchen, werden wir sehen:public static double pow(double a, double b) { return StrictMath.pow(a, b); // default impl. delegates to StrictMath }
Es steht der JVM jedoch frei, sie mit Eigen- oder Laufzeitaufrufen zu implementieren. Daher kann das zurückkehrende Ergebnis von dem abweichen, was wir erwarten würden
StrictMath.pow
.Und der folgende Code zeigt diesen Aufruf
Math.pow()
gegenStrictMath.pow()
//Strict.java, testing StrictMath.pow against Math.pow import java.util.Random; public class Strict { static double testIt(double x, double y) { return Math.pow(x, y); } public static void main(String[] args) throws Exception{ final double[] vs = new double[100]; final double[] xs = new double[100]; final double[] ys = new double[100]; final Random random = new Random(); // compute StrictMath.pow results; for (int i = 0; i<100; i++) { xs[i] = random.nextDouble(); ys[i] = random.nextDouble(); vs[i] = StrictMath.pow(xs[i], ys[i]); } boolean printed_compiled = false; boolean ever_diff = false; long len = 1000000; long start; long elapsed; while (true) { start = System.currentTimeMillis(); double blackhole = 0; for (int i = 0; i < len; i++) { int idx = i % 100; double res = testIt(xs[idx], ys[idx]); if (i >= 0 && i<100) { //presumably interpreted if (vs[idx] != res && (!Double.isNaN(res) || !Double.isNaN(vs[idx]))) { System.out.println(idx + ":\tInterpreted:" + xs[idx] + "^" + ys[idx] + "=" + res); System.out.println(idx + ":\tStrict pow : " + xs[idx] + "^" + ys[idx] + "=" + vs[idx] + "\n"); } } if (i >= 250000 && i<250100 && !printed_compiled) { //presumably compiled at this time if (vs[idx] != res && (!Double.isNaN(res) || !Double.isNaN(vs[idx]))) { System.out.println(idx + ":\tcompiled :" + xs[idx] + "^" + ys[idx] + "=" + res); System.out.println(idx + ":\tStrict pow :" + xs[idx] + "^" + ys[idx] + "=" + vs[idx] + "\n"); ever_diff = true; } } } elapsed = System.currentTimeMillis() - start; System.out.println(elapsed + " ms "); if (!printed_compiled && ever_diff) { printed_compiled = true; return; } } } }
Ich habe diesen Test mit OpenJDK 8u5-b31 durchgeführt und das folgende Ergebnis erhalten:
10: Interpreted:0.1845936372497491^0.01608930867480518=0.9731817015518033 10: Strict pow : 0.1845936372497491^0.01608930867480518=0.9731817015518032 41: Interpreted:0.7281259501809544^0.9414406865385655=0.7417808233050295 41: Strict pow : 0.7281259501809544^0.9414406865385655=0.7417808233050294 49: Interpreted:0.0727813262968815^0.09866028976654662=0.7721942440239148 49: Strict pow : 0.0727813262968815^0.09866028976654662=0.7721942440239149 70: Interpreted:0.6574309575966407^0.759887845481148=0.7270872740201638 70: Strict pow : 0.6574309575966407^0.759887845481148=0.7270872740201637 82: Interpreted:0.08662340816125613^0.4216580281197062=0.3564883826345057 82: Strict pow : 0.08662340816125613^0.4216580281197062=0.3564883826345058 92: Interpreted:0.20224488115245098^0.7158182878844233=0.31851834311978916 92: Strict pow : 0.20224488115245098^0.7158182878844233=0.3185183431197892 10: compiled :0.1845936372497491^0.01608930867480518=0.9731817015518033 10: Strict pow :0.1845936372497491^0.01608930867480518=0.9731817015518032 41: compiled :0.7281259501809544^0.9414406865385655=0.7417808233050295 41: Strict pow :0.7281259501809544^0.9414406865385655=0.7417808233050294 49: compiled :0.0727813262968815^0.09866028976654662=0.7721942440239148 49: Strict pow :0.0727813262968815^0.09866028976654662=0.7721942440239149 70: compiled :0.6574309575966407^0.759887845481148=0.7270872740201638 70: Strict pow :0.6574309575966407^0.759887845481148=0.7270872740201637 82: compiled :0.08662340816125613^0.4216580281197062=0.3564883826345057 82: Strict pow :0.08662340816125613^0.4216580281197062=0.3564883826345058 92: compiled :0.20224488115245098^0.7158182878844233=0.31851834311978916 92: Strict pow :0.20224488115245098^0.7158182878844233=0.3185183431197892 290 ms
Bitte beachten Sie, dass dies
Random
zum Generieren der x- und y-Werte verwendet wird, sodass Ihr Kilometerstand von Lauf zu Lauf variiert. Aber eine gute Nachricht ist, dass zumindest die Ergebnisse der kompilierten Version vonMath.pow
denen der interpretierten Version von übereinstimmenMath.pow
. (Off Topic: Auch diese Konsistenz wurde erst 2012 mit einer Reihe von Bugfixes von OpenJDK-Seite durchgesetzt.)Der Grund?
Das liegt daran, dass OpenJDK zur Implementierung
Math.pow
(und anderer mathematischer Funktionen) intrinsische und Laufzeitfunktionen verwendet , anstatt nur den Java-Code auszuführen. Der Hauptzweck besteht darin, x87-Anweisungen zu nutzen, damit die Leistung für die Berechnung gesteigert werden kann. DaherStrictMath.pow
wirdMath.pow
zur Laufzeit nie von aufgerufen (für die OpenJDK-Version, die wir gerade verwendet haben, um genau zu sein).Und diese Anordnung ist laut dem Javadoc der
Math
Klasse (ebenfalls von @coobird oben zitiert) völlig legitim :Und die Schlussfolgerung? Stellen Sie für Sprachen mit dynamischer Codegenerierung wie Java sicher, dass das, was Sie aus dem 'statischen' Code sehen, mit dem übereinstimmt, was zur Laufzeit ausgeführt wird. Ihre Augen können Sie manchmal wirklich irreführen.
quelle
Haben Sie den Quellcode überprüft? Viele Methoden in
java.lang.Math
werden an delegiertjava.lang.StrictMath
.Beispiel:
public static double cos(double a) { return StrictMath.cos(a); // default impl. delegates to StrictMath }
quelle
Math
wird nicht wirklich verwendetStrictMath
.Zitieren von java.lang.Math :
...
Und dann sehen wir unter Math.pow (..) zum Beispiel:
Was ist nun der Ulp? Wie erwartet
java.lang.Math.ulp(1.0)
ergibt sich 2.220446049250313e-16, was 2 -52 ist . (Math.ulp(8)
Gibt auch den gleichen Wert wieMath.ulp(10)
und anMath.ulp(15)
, aber nichtMath.ulp(16)
.) Mit anderen Worten, wir sprechen über das letzte Stück der Mantisse.Das von zurückgegebene Ergebnis
java.lang.Math.pow(..)
kann also im letzten der 52 Bits der Mantisse falsch sein, wie wir in Tony Guans Antwort bestätigen können.Es wäre schön, einen konkreten 1-ulp- und 0,5-ulp-Code zum Vergleich auszugraben. Ich werde spekulieren, dass ziemlich viel zusätzliche Arbeit erforderlich ist, um das letzte Bit korrekt zu machen, aus dem gleichen Grund, aus dem wir, wenn wir zwei Zahlen A und B kennen, auf 52 signifikante Zahlen gerundet sind und A × B auf 52 signifikante Zahlen korrigieren möchten Bei korrekter Rundung müssen wir tatsächlich einige zusätzliche Bits von A und B kennen, um das letzte Bit von A × B richtig zu machen. Das heißt aber, wir sollten die Zwischenergebnisse A und B nicht runden, indem wir sie zu Doppel erzwingen . Wir benötigen effektiv einen breiteren Typ für Zwischenergebnisse. (Wie ich gesehen habe, beruhen die meisten Implementierungen mathematischer Funktionen stark auf Multiplikationen mit hartcodierten vorberechneten Koeffizienten. Wenn sie also breiter als doppelt sein müssen, gibt es einen großen Effizienzverlust.)
quelle