Ovviamente java.lang.StrictMath
contiene funzioni aggiuntive (iperboliche ecc.) Che non è disponibile in java.lang.Math
, ma esiste una differenza nelle funzioni che si trovano in entrambe le librerie?Qual è la differenza tra java.lang.Math e java.lang.StrictMath?
risposta
Il Javadoc per la classe Math
fornisce alcune informazioni sulle differenze tra le due classi:
A differenza dei metodi numerici di classe
StrictMath
, tutte le implementazioni delle funzioni equivalenti della classeMath
non sono definite per restituire gli stessi risultati dello bit-per-bit. Questo rilassamento consente implementazioni con prestazioni migliori laddove non è richiesta una riproducibilità rigorosa .di default molti dei metodi
Math
semplicemente chiamare il metodo equivalente inStrictMath
per la loro attuazione. generatori di codice sono incoraggiati ad utilizzare librerie native specifiche della piattaforma o istruzioni microprocessore, dove a disposizione, per fornire implementazioni più performanti diMath
metodi. Tali implementazioni a prestazioni superiori devono ancora essere conformi a le specifiche perMath
.
Pertanto, la classe Math
espone alcune regole su ciò che alcune operazioni dovrebbero fare, ma non chiedere che il esatte stessi risultati essere restituiti in tutte le implementazioni delle librerie.
Ciò consente alle implementazioni specifiche delle librerie di restituire lo stesso risultato, ma non lo stesso identico se, ad esempio, viene chiamata la classe Math.cos
. Ciò consentirebbe implementazioni specifiche della piattaforma (come l'uso di virgola mobile x86 e, ad esempio, di virgola mobile SPARC) che potrebbero restituire risultati diversi.
(consultare la sezione Software Implementations della Sine articolo Wikipedia per alcuni esempi di implementazioni specifiche della piattaforma.)
Tuttavia, con StrictMath
, i risultati restituiti da diverse implementazioni devono restituire lo stesso risultato . Ciò sarebbe auspicabile nei casi in cui è richiesta la riproducibilità dei risultati su piattaforme diverse.
Ma perché diverse implementazioni specifiche della piattaforma vogliono produrre risultati diversi? Il coseno non è universalmente definito? – Aivar
@Aivar: per i motivi elencati nella citazione dalla classe 'Math' - per sfruttare i metodi nativi disponibili per la piattaforma specifica, che è (in molti casi è probabile che sia) più veloce rispetto all'utilizzo di una soluzione basata su software che è garantito per dare esattamente la stessa risposta su tutte le piattaforme. – coobird
ok, quindi significa che alcune piattaforme hanno scelto di non calcolare la risposta più precisa che si adatta a determinate quantità di bit, ma hanno scambiato la precisione per l'efficienza? E diverse piattaforme hanno fatto diversi compromessi? – Aivar
Hai controllato il codice sorgente? Molti metodi in java.lang.Math
sono delegati a java.lang.StrictMath
.
Esempio:
public static double cos(double a) {
return StrictMath.cos(a); // default impl. delegates to StrictMath
}
+1 per la lettura del codice sorgente Java. Questo è un punto di forza per Java su .NET: una grande parte del codice sorgente dell'API Java viene fornita con il JDK in un file chiamato src.zip. E quello che non c'è può essere scaricato ora che la JVM è open source. Leggere il sorgente Java potrebbe non essere il modo più pubblicizzato per risolvere i problemi: potrebbe sembrare una cattiva idea, in quanto si suppone che "si attiene all'interfaccia pubblica e non all'implementazione". Tuttavia, leggere la fonte ha un forte vantaggio: ti darà sempre la verità. E a volte questa è la cosa più preziosa di tutte. –
@MikeClark Puoi anche leggere le fonti per .NET. –
@Andrew Grazie per il suggerimento. Ho appena finito di leggere un tutorial su come configurarlo in Visual Studio. Java può ancora avere un leggero vantaggio in quanto è possibile scaricare il codice sorgente per la VM stessa, non solo la sua libreria standard (framework). Comunque grazie! –
@ntoskrnl Come qualcuno che sta lavorando con internamenti JVM, vorrei secondo la tua opinione che "gli intrinseci non si comportano necessariamente allo stesso modo dei metodi StrictMath". Per scoprirlo (o dimostrarlo), possiamo semplicemente scrivere un semplice test.
Prendere ad esempio Math.pow
, esaminare il codice Java per java.lang.Math.pow (double a, doppia b), vedremo:
public static double pow(double a, double b) {
return StrictMath.pow(a, b); // default impl. delegates to StrictMath
}
Ma la JVM è libero di implementare con intrinseche o chiamate di runtime, quindi il risultato di ritorno può essere diverso da quello che ci si aspetterebbe da StrictMath.pow
.
E il seguente codice mostra questo chiamando Math.pow()
contro StrictMath.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;
}
}
}
}
ho eseguito questo test con OpenJDK 8u5-B31 e ottenuto il risultato qui sotto:
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
prega di notare che Random
viene utilizzato per genera i valori xey, quindi il tuo chilometraggio varierà da run a run. Ma la buona notizia è che almeno i risultati della versione compilata di Math.pow
corrispondono a quelli della versione interpretata di Math.pow
. (Off topic: anche questa coerenza è stata applicata solo nel 2012 con una serie di correzioni di bug dal lato OpenJDK.)
Il motivo?
Bene, è perché OpenJDK utilizza le funzioni intrinseche e di runtime per implementare Math.pow
(e altre funzioni matematiche), invece di eseguire semplicemente il codice Java. Lo scopo principale è sfruttare le istruzioni x87 in modo che le prestazioni per il calcolo possano essere potenziate. Di conseguenza, StrictMath.pow
non viene mai chiamato da Math.pow
in fase di esecuzione (per la versione OpenJDK che abbiamo appena utilizzato, per la precisione).
E questo arragement è totalmente legittima secondo Javadoc di Math
classe (anche citato da @coobird sopra):
La classe Math contiene i metodi per eseguire operazioni numeriche fondamentali, come elementari esponenziale, logaritmo, radice quadrata e funzioni trigonometriche.
A differenza di alcuni dei metodi numerici della classe StrictMath, tutte le implementazioni delle funzioni equivalenti della classe Math non sono definite per restituire gli stessi risultati bit-for-bit. Questo rilassamento consente implementazioni più performanti laddove non è richiesta una rigida riproducibilità.
Per impostazione predefinita molti dei metodi matematici chiamano semplicemente il metodo equivalente in StrictMath per la loro implementazione. I generatori di codice sono invitati a utilizzare librerie native specifiche della piattaforma o istruzioni del microprocessore, laddove disponibili, per fornire implementazioni più elevate dei metodi matematici. Tali implementazioni a prestazioni più elevate devono ancora essere conformi alle specifiche per la matematica.
E la conclusione? Bene, per le lingue con generazione di codice dinamica come Java, assicurati che ciò che vedi dal codice 'statico' corrisponda a ciò che viene eseguito in fase di runtime. I tuoi occhi a volte possono davvero ingannarti.
Citando java.lang.Math:
precisione della virgola mobile
Math
metodi è misurato in termini di ulps, unità all'ultimo posto.
...
Se un metodo presenta sempre un errore inferiore a 0,5 ulps, il metodo sempre restituisce il numero in virgola mobile più vicino al risultato esatto; tale metodo è arrotondato correttamente. Un metodo correttamente arrotondato è generalmente il migliore che un'approssimazione a virgola mobile può essere; tuttavia, non è pratico che molti metodi in virgola mobile siano arrotondati correttamente.
E poi vediamo sotto Math.pow(..), ad esempio:
Il risultato calcolato deve essere entro 1 ulp del risultato esatto.
Ora, qual è l'ulp? Come previsto, java.lang.Math.ulp(1.0)
fornisce 2.220446049250313e-16, ovvero 2 -52. (Anche Math.ulp(8)
fornisce lo stesso valore di Math.ulp(10)
e Math.ulp(15)
, ma non Math.ulp(16)
.) In altre parole, stiamo parlando dell'ultimo bit della mantissa.
Quindi, il risultato restituito da java.lang.Math.pow(..)
potrebbe essere errato nell'ultimo dei 52 bit della mantissa, come possiamo confermare nella risposta di Tony Guan.
Sarebbe bello scavare un po 'di codice 1 ulp e 0,5 ulp di calcestruzzo da confrontare. Immagino che sia necessario un bel po 'di lavoro in più per ottenere l'ultimo bit corretto per lo stesso motivo per cui se conosciamo due numeri A e B arrotondati a 52 cifre significative e desideriamo sapere A × B corretto a 52 cifre significative , con arrotondamenti corretti, quindi in realtà abbiamo bisogno di conoscere alcuni bit in più di A e B per ottenere l'ultimo bit di A × B a destra. Ma ciò significa che non dovremmo arrotondare i risultati intermedi A e B forzandoli in doppi, abbiamo bisogno, effettivamente, di un tipo più ampio per risultati intermedi. (In quello che ho visto, la maggior parte delle implementazioni di funzioni matematiche dipendono fortemente dalle moltiplicazioni con coefficienti precompilati codificati, quindi se hanno bisogno di essere più larghe del doppio, c'è un grande colpo di efficienza.)
- 1. Qual è la differenza tra = e: =
- 2. Qual è la differenza tra Verilog! e ~?
- 3. Qual è la differenza tra? : e ||
- 4. qual è la differenza tra [[], []] e [[]] * 2
- 5. Qual è la differenza tra $ e $$?
- 6. Qual è la differenza tra ("") e (null)
- 7. Qual è la differenza tra dict() e {}?
- 8. Qual è la differenza tra " " e ""?
- 9. Qual è la differenza tra {0} e ""?
- 10. Qual è la differenza tra `##` e `hashCode`?
- 11. Qual è la differenza tra {0} e +?
- 12. Qual è la differenza tra .ToString() e + ""
- 13. qual è la differenza tra:.! e: r !?
- 14. Qual è la differenza tra "è Nessuno" e "== Nessuno"
- 15. Unix: Qual è la differenza tra la fonte e l'esportazione?
- 16. Qual è la differenza tra il callback e la promessa
- 17. Qual è la differenza tra la sezione .got e .got.plt?
- 18. Qual è la differenza tra la lista() e []
- 19. Qual è la differenza tra GHC e la piattaforma Haskell?
- 20. Qual è la differenza tra sé e la finestra?
- 21. Qual è la differenza tra la cartella vim72 e vimfiles?
- 22. Qual è la differenza tra la serratura e RLock
- 23. Qual è la differenza tra la funzione() {}() e function() {}()
- 24. Qual è la differenza tra la crittografia SHA e AES?
- 25. Qual è la differenza tra la [OptionalField] e [NonSerialized]
- 26. Qual è la differenza tra Chisel e Lava e CLaSH?
- 27. Qual è la differenza tra queste funzioni
- 28. Qual è la differenza tra queste funzioni?
- 29. Qual è la differenza tra Response.Write() eResponse.Output.Write()?
- 30. Qual è la differenza tra applicationDidReceiveMemoryWarning, didReceiveMemoryWarning?
Questa domanda è interamente risposta in il Javadoc. – EJP
@EJP - Credo che, su SO, RTFM non sia mai una buona risposta. – ripper234