2010-11-20 8 views

risposta

59

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 classe Math 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 in StrictMath 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 di Math metodi. Tali implementazioni a prestazioni superiori devono ancora essere conformi a le specifiche per Math.

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.

+1

Ma perché diverse implementazioni specifiche della piattaforma vogliono produrre risultati diversi? Il coseno non è universalmente definito? – Aivar

+0

@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

+0

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

18

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 
} 
+14

+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. –

+4

@MikeClark Puoi anche leggere le fonti per .NET. –

+1

@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! –

7

@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.

0

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.)

Problemi correlati