78 votes

Quelle est la différence entre java.lang.Math et java.lang.StrictMath ?

Évidemment java.lang.StrictMath contient des fonctions supplémentaires (hyperboliques, etc.) qui java.lang.Math mais y a-t-il une différence dans les fonctions qui se trouvent dans les deux bibliothèques ?

6 votes

La réponse à cette question se trouve entièrement dans la Javadoc.

34 votes

@EJP - Je crois que, sur le SO, RTFM n'est jamais une bonne réponse.

75voto

coobird Points 70356

La Javadoc pour le Math fournit quelques informations sur les différences entre les deux classes :

Contrairement à certaines méthodes numériques de classe StrictMath toutes les mises en œuvre des fonctions équivalentes de la classe Math ne sont pas définis pour retourner le même résultat bit à bit. Ce site relaxation permet des implémentations plus performantes meilleures performances lorsque la reproductibilité stricte n'est pas requise.

Par défaut, de nombreuses Math méthodes appellent simplement la méthode équivalente dans StrictMath pour leur mise en œuvre. Les générateurs de code sont encouragés à utiliser des bibliothèques natives spécifiques à la plate-forme ou instructions de microprocesseur, lorsque disponibles, afin de fournir des implémentations plus performantes de Math méthodes. Ces implémentations plus performantes doivent toujours se conformer à la la spécification pour Math .

Par conséquent, le Math établit quelques règles sur ce que certaines opérations doivent faire, mais elles n'exigent pas que la classe exact les mêmes résultats soient renvoyés dans toutes les implémentations des bibliothèques.

Cela permet à des implémentations spécifiques des bibliothèques de renvoyer un résultat similaire, mais pas exactement le même, si, par exemple, la fonction Math.cos est appelée. T

_(Voir le Implémentation de logiciels de la section Sine dans Wikipedia pour quelques exemples d'implémentations spécifiques à une plateforme)._

Cependant, avec StrictMath les résultats renvoyés par différentes implémentations doit renvoient le même résultat. Cela serait souhaitable dans les cas où la reproductibilité des résultats sur différentes plateformes est requise.

3 votes

Mais pourquoi différentes implémentations spécifiques à une plateforme voudraient-elles produire des résultats différents ? Le cosinus n'est-il pas universellement défini ?

0 votes

@Aivar : Pour les raisons énumérées dans la citation du Math pour tirer parti des méthodes natives disponibles sur la plate-forme spécifique, ce qui est (dans de nombreux cas) plus rapide que l'utilisation d'une solution logicielle qui est garantie de donner exactement la même réponse sur toutes les plates-formes.

0 votes

Ok, donc cela signifie que certaines plateformes ont choisi de ne pas calculer la réponse la plus précise qui tienne dans des quantités données de bits, mais ont échangé la précision contre l'efficacité ? Et différentes plates-formes ont fait différents compromis ?

26voto

Tony Guan Points 411

@ntoskrnl En tant que personne travaillant avec les internes de la JVM, je voudrais appuyer votre opinion selon laquelle "les intrinsèques ne se comportent pas nécessairement de la même manière que les méthodes StrictMath". Pour le savoir (ou le prouver), nous pouvons simplement écrire un test simple.

Prenez Math.pow par exemple, en examinant le code Java de java.lang.Math.pow(double a, double b), nous verrons :

 public static double pow(double a, double b) {
    return StrictMath.pow(a, b); // default impl. delegates to StrictMath
}

Mais la JVM est libre de l'implémenter avec des intrinsèques ou des appels au moment de l'exécution, ainsi le résultat retourné peut être différent de ce que nous attendons de StrictMath.pow .

Et le code suivant montre cet appel Math.pow() contre 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;
            }

        }
    }
}

J'ai effectué ce test avec OpenJDK 8u5-b31 et j'ai obtenu le résultat ci-dessous :

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 

Veuillez noter que Random est utilisé pour générer les valeurs x et y, donc votre kilométrage variera d'une exécution à l'autre. Mais la bonne nouvelle est qu'au moins les résultats de la version compilée de Math.pow correspondent à celles de la version interprétée de Math.pow . (Hors sujet : même cette cohérence n'a été mise en application qu'en 2012 avec une série de corrections de bugs du côté d'OpenJDK).

La raison ?

Eh bien, c'est parce que OpenJDK utilise des fonctions intrinsèques et des fonctions d'exécution pour mettre en oeuvre Math.pow (et autres fonctions mathématiques), au lieu de simplement exécuter le code Java. L'objectif principal est de tirer parti des instructions x87 afin d'augmenter les performances du calcul. En conséquence, StrictMath.pow n'est jamais appelé depuis Math.pow au moment de l'exécution (pour la version d'OpenJDK que nous venons d'utiliser, pour être précis).

Et cet arrangement est tout à fait légitime selon la Javadoc de Math (également cité par @coobird ci-dessus) :

La classe Math contient des méthodes permettant d'effectuer des opérations numériques de base, telles que les fonctions exponentielles, logarithmiques, racine carrée et trigonométriques élémentaires.

Contrairement à certaines des méthodes numériques de la classe StrictMath, toutes les implémentations des fonctions équivalentes de la classe Math ne sont pas définies pour retourner les mêmes résultats bit à bit. Cet assouplissement permet des implémentations plus performantes lorsque la reproductibilité stricte n'est pas requise.

Par défaut, de nombreuses méthodes mathématiques appellent simplement la méthode équivalente dans StrictMath pour leur mise en œuvre. Les générateurs de code sont encouragés à utiliser des bibliothèques natives spécifiques à la plate-forme ou des instructions de microprocesseur, lorsqu'elles sont disponibles, pour fournir des implémentations plus performantes des méthodes Math. Ces implémentations plus performantes doivent néanmoins se conformer à la spécification de Math.

Et la conclusion ? Eh bien, pour les langages avec génération de code dynamique tels que Java, assurez-vous que ce que vous voyez du code "statique" correspond à ce qui est exécuté au moment de l'exécution. Vos yeux peuvent parfois vous induire en erreur.

19voto

z00bs Points 5664

Avez-vous vérifié le code source ? De nombreuses méthodes dans java.lang.Math sont déléguées à java.lang.StrictMath .

Exemple :

public static double cos(double a) {
    return StrictMath.cos(a); // default impl. delegates to StrictMath
}

19 votes

+1 pour la lecture du code source Java. C'est un point fort de Java par rapport à .NET : une grande partie du code source de l'API Java est fournie avec le JDK dans un fichier appelé src.zip. Et ce qui ne s'y trouve pas peut être téléchargé maintenant que la JVM est à source ouverte. Lire le code source de Java

1 votes

@Andrew Merci pour le conseil. Je viens de finir de lire un tutoriel sur la façon de configurer cela dans Visual Studio. Java a peut-être encore un léger avantage dans la mesure où vous pouvez télécharger le code source de la VM elle-même, et pas seulement sa bibliothèque standard (framework). En tout cas, merci !

12 votes

Malheureusement, dans ce cas, le code source ne dit pas toute la vérité. La JVM est libre de remplacer les méthodes de Math par des intrinsèques spécifiques à la plate-forme. Les intrinsèques ne se comportent pas nécessairement de la même manière que les méthodes StrictMath, mais leur comportement es contraint par la documentation de la classe Math.

0voto

Evgeni Sergeev Points 1517

Citation : java.lang.Math :

Précision de la virgule flottante Math Les méthodes sont mesurées en termes de ulps , unités à la dernière place.

...

Si une méthode a toujours une erreur inférieure à 0,5 ulps, la méthode retourne toujours renvoie toujours le nombre à virgule flottante le plus proche du résultat exact. méthode est correctement arrondis . Une méthode correctement arrondie est généralement la meilleure approximation possible en virgule flottante ; cependant, il n'est pas pratique pour de nombreuses méthodes en virgule flottante d'être correctement arrondies.

Et ensuite nous voyons sous Math.pow(..) par exemple :

Le résultat calculé doit se situer à moins de 1 ulp du résultat exact.

Maintenant, qu'est-ce que l'ulp ? Comme prévu, java.lang.Math.ulp(1.0) donne 2.220446049250313e-16, soit 2 -52 . (Aussi Math.ulp(8) donne la même valeur que Math.ulp(10) y Math.ulp(15) mais pas Math.ulp(16) .) En d'autres termes, nous parlons du dernier bit de la mantisse.

Ainsi, le résultat renvoyé par java.lang.Math.pow(..) peut être erroné dans le dernier des 52 bits de la mantisse, comme nous pouvons le confirmer dans la réponse de Tony Guan.

Ce serait bien de déterrer du code concret à 1 ulp et 0,5 ulp pour comparer. Je suppose que beaucoup de travail supplémentaire est nécessaire pour obtenir ce dernier bit correct pour la même raison que si nous connaissons deux nombres A et B arrondis à 52 chiffres significatifs et que nous souhaitons connaître A×B correctement à 52 chiffres significatifs, avec un arrondi correct, alors en fait nous devons connaître quelques bits supplémentaires de A et B pour obtenir le dernier bit de A×B correct. Mais cela signifie que nous ne devrions pas arrondir les résultats intermédiaires A et B en les forçant à entrer dans le format doubles nous avons besoin, en fait, d'un type plus large pour les résultats intermédiaires. (Dans ce que j'ai vu, la plupart des implémentations de fonctions mathématiques s'appuient fortement sur des multiplications avec des coefficients précalculés codés en dur, donc s'ils doivent être plus larges que le double, il y a un gros coup d'efficacité).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X