2
votes

Math.pow () dans Java 8 et Java 13 renvoie un résultat différent

Le code suivant renvoie des résultats différents sur Java 8 et Java 11.

val = 1.000003058823805100000000

Java 8:

val = 1.000003058823805400000000

Java 11: (le même résultat que Python, Rust)

class Playground {
    public static void main(String[ ] args) {
        long x = 29218;
        long q = 4761432;
        double ret = Math.pow(1.0 + (double) x / q, 0.0005);
        System.out.println("val = " + String.format("%.24f", ret));
    }
}

Les questions sont les suivantes:

  • Une documentation pour décrire ce genre de comportement?
  • Comment implémenter Math.pow() Java 8 en Python?
  • Comment garantir une cohérence stricte avec les anciens programmes utilisant la bibliothèque Java 8?


10 commentaires

Est-ce une valeur différente ou est-ce que String.format() se comporte différemment?


valeur différente


Comment savez-vous que c'est une valeur différente?


Ces java sont-ils tous les deux sur la même machine?


Une question, pourquoi le dernier chiffre est-il important? Pour la plupart des applications, les 16 chiffres décimaux significatifs fournis sont plus que suffisants. Vous voudrez peut-être plus de chiffres de précision, mais dans ces cas, vous voudriez une technique différente, par exemple une bibliothèque effectuant tous les calculs en utilisant des doubles longs avec plus de 30 chiffres décimaux.


@Salixalba Lorsque vous devez être strictement cohérent avec les anciens programmes.


Lorsque vous avez besoin de résultats strictement cohérents, votre programme doit avoir utilisé StrictMath.pow(…) en premier lieu. Essayer d'obtenir une «cohérence stricte» avec une implémentation particulière de l'API non stricte n'a aucun sens.


@Holger Je n'écris pas de programmes java. J'ai besoin de copier le comportement dans une autre langue.


Pourquoi? Il n'y a toujours pas de sens dans cette tâche.


@Holger Dans la programmation quotidienne, on ne rencontre jamais cela. Mais lorsque vous devez copier le comportement d'un ancien programme ou système et que toutes les anciennes données générées doivent être reproductibles, vous devez l'implémenter. Donc, avec un assemblage 8087, je l'ai fait.


3 Réponses :


11
votes

Si nous utilisons simplement Double.toString () pour imprimer les réponses, les deux résultats différents seraient

1.000033518c576
1.000033518c575

Les chiffres supplémentaires à la fin ne sont qu'un facteur de String.format (). Nous voyons que les nombres ne diffèrent que par le dernier chiffre décimal significatif. Conversion des deux nombres en hexadécimal

1.0000030588238054
1.0000030588238051

donc les représentations binaires ne diffèrent que d'une unité à la dernière place (ulp).

En lisant la spécification de Math.pow, nous trouvons

"Le résultat calculé doit être à 1 ulp du résultat exact."

La vraie valeur est proche de 1.000003058823805246468 ( WolframAlpha ) quelque part entre les deux réponses, donc les deux sont dans la spécification.

Tout ce qui s'est passé, c'est que la bibliothèque a subi un léger changement d'algorithme, peut-être pour la rendre plus rapide.


2 commentaires

J'aurais pu sauter mon travail si vous étiez encore un peu plus rapide. Mais de toute façon, deux manières d'expliquer la même chose ne sont pas forcément mauvaises. Je vous salue et +1 pour avoir répondu le plus rapidement…


Merci, mais ma question est sans réponse.



7
votes

Notez que

1.0000030588238054         JDK  8
1.000003058823805246…      Wolfram Alpha
1.0000030588238051         JDK 11

est un mélange inutile de formatage de style printf , concaténation de chaînes et println . Vous pouvez utiliser printf en premier lieu:

1.000003058823805246…

Cependant, il est inutile de demander 24 chiffres décimaux, lorsque la double précision ne fournit même pas à distance autant de chiffres. Lorsque vous utilisez

double d1 = 1.0000030588238051, d2 = 1.0000030588238054;
System.out.println((d2 - d1) == Math.ulp(d1));

au lieu de cela, il utilisera par défaut les chiffres réellement disponibles, ce qui donne

val = 1.0000030588238051

pour Java 8 et

val = 1.0000030588238054

pour Java 11.

Donc, la différence n'est que dans le dernier chiffre. Ou plus précisément

System.out.println("val = " + ret);

imprime true , donc la distance entre ces deux valeurs est la plus petite possible avec double . Il n'y a pas d'autre double valeur entre eux. La spécification de pow dit :

Le résultat calculé doit être à moins de 1 ulp du résultat exact.

Puisque le code ci-dessus a montré que les deux résultats ont une distance d'un ulp, les deux résultats seraient corrects lorsque le résultat exact se situe entre les deux résultats. Wolfram Alpha dit, le résultat exact commence par

System.out.printf("val = %.24f%n", ret);

C'est donc entre ces deux résultats. Les deux résultats sont donc corrects selon la spécification.

Pour une comparaison plus facile:

System.out.println("val = " + String.format("%.24f", ret));


3 commentaires

Merci, mais ma question est sans réponse.


Ensuite, «votre question» n'est pas la question que vous avez postée. Vous avez demandé une «documentation décrivant ce genre de comportement» et vous l'avez obtenue.


Google le mot-clé strictfp et c'est la réponse.



2
votes

Après quelques recherches, la réponse rapide est strictfp . réf: https://en.wikipedia.org/wiki/Strictfp

Dans java8, le comportement par défaut est d'utiliser le FPU x87 sur les machines x86, qui utilisera le double étendu (80 bits) comme valeurs intermédiaires. Ainsi, le résultat ne sera pas garanti d'avoir la même valeur sur une autre plateforme.

Dans java11 ou java13, le comportement par défaut utilise IEEE double (64 bits) comme valeurs intermédiaires.

Puisque presque tous les langages modernes garantissent des résultats stables via les flotteurs IEEE, ce comportement est difficile à imiter dans d'autres langages ou dans le nouveau java. L'assemblage FPU x87 en ligne peut aider.


0 commentaires