Question

Je code plusieurs algorithmes de référence à la fois en Java et en C / C ++. Certains de ces algorithmes utilisent p. J'aimerais que les deux implémentations de chaque algorithme produisent des résultats identiques , sans arrondir différemment. Une façon de procéder qui a fonctionné de manière cohérente jusqu’à présent consiste à utiliser une constante pi personnalisée qui est exactement la même dans les deux langues, telle que 3.14159. Cependant, il me semble idiot de définir pi quand il existe déjà des constantes de haute précision définies dans les bibliothèques Java et GCC.

J'ai passé un certain temps à écrire des programmes de test rapides, à consulter la documentation de chaque bibliothèque et à lire des types à virgule flottante. Mais je n'ai pas réussi à me convaincre que java.lang.Math.PI (ou java.lang.StrictMath.PI) est ou n'est pas égal à M_PI en math.h.

GCC 3.4.4 (cygwin) math.h contient:

#define M_PI            3.14159265358979323846
                                         ^^^^^

mais cela

printf("%.20f", M_PI);

produit

3.14159265358979311600
                 ^^^^^

ce qui suggère que les 5 derniers chiffres ne sont pas fiables.

Pendant ce temps, les Javadocs disent que java.lang.Math.PI est:

  

La valeur double qui est plus proche de   tout autre à pi , le rapport entre le   circonférence d'un cercle à sa   diamètre.

et

public static final double PI  3.141592653589793d

qui omet les cinq derniers chiffres discutables de la constante.

System.out.printf("%.20f\n", Math.PI);

produit

3.14159265358979300000
                 ^^^^^

Si vous connaissez les types de données à virgule flottante, pouvez-vous me convaincre que ces constantes de bibliothèque sont exactement égales? Ou qu'ils ne sont définitivement pas égaux?

Était-ce utile?

La solution 4

Oui, ils sont égaux et leur utilisation garantira que les implémentations GCC et Java du même algorithme sont sur le même pied - au moins autant que d'utiliser une constante pi définie manuellement. sup> † .

Une mise en garde, suggérée par S. Lott , c’est que l’implémentation GCC doit contenir M_PI dans un type de données double et non long double pour assurer l’équivalence. Il semble que Java et GCC utilisent tous deux la représentation décimale 64 bits de IEEE-754 pour leurs types de données double respectifs. La représentation bytewise (MSB à LSB) de la valeur de la bibliothèque, exprimée sous la forme double , peut être obtenue comme suit (grâce à JeeBee ):

pi_bytes.c:

#include <math.h>
#include <stdio.h>
int main()
{
   double pi = M_PI;
   printf("%016llx\n", *((uint64_t*)&pi));
}

pi_bytes.java:

class pi_bytes
{
   public static void main(String[] a)
   {
      System.out.printf("%016x\n", Double.doubleToRawLongBits( Math.PI ) );
   }
}

Exécuter les deux:

$ gcc -lm -o pi_bytes pi_bytes.c && ./pi_bytes
400921fb54442d18

$ javac pi_bytes.java && java pi_bytes
400921fb54442d18

Les représentations sous-jacentes de M_PI (sous forme de double ) et de Math.PI sont identiques, leurs bits le plus bas.

† - Comme indiqué par Steve Schnepp , le résultat de fonctions mathématiques telles que sin, cos, exp, etc. n'est pas garanti d'être identique, même si les entrées de ces calculs sont identiques au niveau du bit.

Autres conseils

Notez ce qui suit.

Les deux nombres sont identiques à 16 décimales. C'est presque 48 bits qui sont les mêmes.

Dans un nombre à virgule flottante IEEE 64 bits, ce sont tous les bits qui ne sont ni des signes ni des exposants.

Le #define M_PI a 21 chiffres; c'est environ 63 bits de précision, ce qui est bon pour une valeur à virgule flottante IEEE 80 bits.

Je pense que vous constatez une troncature ordinaire des bits de la valeur M_PI .

Ce que vous voulez faire, c'est imprimer le motif de bits brut pour les valeurs PI et les comparer.

En Java, utilisez http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Double.html#doubleToRawLongBits (double) méthode pour obtenir la valeur longue à imprimer au format binaire.

Java 5 donne:

  • PI est 3.141592653589793
  • Les bits bruts correspondent à 4614256656552045848
  • binaire est 1000000000010010010010011111101010100010001000010110100011000

En C, vous pouvez faire double pi = M_PI; printf ("% lld \ n", pi); pour obtenir le même entier de 64 bits: 4614256656552045848 (merci Bruno).

Il serait très difficile de calculer la même valeur, même si les valeurs de départ sont les mêmes .

Les résultats de calcul en virgule flottante sont parfois différents d’une architecture à l’autre (think x86 / PowerPC par exemple), d’un compilateur à un autre (think GCC / MS C ++) et même avec le même compilateur, mais avec des options de compilation différentes. Pas toujours, mais parfois (généralement en arrondissant). Habituellement, juste assez pour que le problème passe inaperçu jusqu’à trop tard (pensez après de nombreuses itérations et de nombreuses différences d’arrondi)

Cela complique les choses pour les jeux multi-joueurs multiplates-formes qui calculent chaque itération du jeu de manière synchrone (chaque nœud reçoit seulement l'entrée, pas les structures de données réelles).

Par conséquent, même si les résultats peuvent être différents dans le même langage (C / C ++), ils peuvent également différer d'une machine virtuelle Java à un hôte natif.

Mise à jour:

Je ne trouve pas la source lue, mais j'ai trouvé un paper par Sun à ce sujet.

Comme vous l'avez vous-même répondu, java.lang.Math.PI et le M_PI de GCC peuvent être gérés avec la même valeur . Le diable se cache dans l'utilisation de ces valeurs. IEEE ne spécifie pas la sortie des fonctions mathématiques (sin, cos, exp, ...). C'est donc la sortie du calcul qui n'est pas nécessairement la même chose.

un double n'a que 52 bits de signfic et je pense donc que cela ne vous donne que 15 chiffres de base à 10 chiffres, ce qui expliquerait pourquoi vous avez 5 zéros lorsque vous demandez 20 chiffres.

Vous pouvez utiliser BigDecimal pour plus de précision, par exemple:

private static final BigDecimal PI = new BigDecimal(
"3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679" +
    "8214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196" +
    "4428810975665933446128475648233786783165271201909145648566923460348610454326648213393607260249141273" +
    "7245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094" +
    "3305727036575959195309218611738193261179310511854807446237996274956735188575272489122793818301194912" +
    "9833673362440656643086021394946395224737190702179860943702770539217176293176752384674818467669405132" +
    "0005681271452635608277857713427577896091736371787214684409012249534301465495853710507922796892589235" +
    "4201995611212902196086403441815981362977477130996051870721134999999837297804995105973173281609631859" +
    "5024459455346908302642522308253344685035261931188171010003137838752886587533208381420617177669147303" +
    "5982534904287554687311595628638823537875937519577818577805321712268066130019278766111959092164201989" +
    "3809525720106548586327886593615338182796823030195203530185296899577362259941389124972177528347913151" +
    "5574857242454150695950829533116861727855889075098381754637464939319255060400927701671139009848824012" +
    "8583616035637076601047101819429555961989467678374494482553797747268471040475346462080466842590694912" +
    "9331367702898915210475216205696602405803815019351125338243003558764024749647326391419927260426992279" +
    "6782354781636009341721641219924586315030286182974555706749838505494588586926995690927210797509302955" +
    "3211653449872027559602364806654991198818347977535663698074265425278625518184175746728909777727938000" +
    "8164706001614524919217321721477235014144197356854816136115735255213347574184946843852332390739414333" +
    "4547762416862518983569485562099219222184272550254256887671790494601653466804988627232791786085784383" +
    "8279679766814541009538837863609506800642251252051173929848960841284886269456042419652850222106611863" +
    "0674427862203919494504712371378696095636437191728746776465757396241389086583264599581339047802759009" +
    "9465764078951269468398352595709825822620522489407726719478268482601476990902640136394437455305068203" +
    "4962524517493996514314298091906592509372216964615157098583874105978859597729754989301617539284681382" +
    "6868386894277415599185592524595395943104997252468084598727364469584865383673622262609912460805124388" +
    "4390451244136549762780797715691435997700129616089441694868555848406353422072225828488648158456028506" +
    "0168427394522674676788952521385225499546667278239864565961163548862305774564980355936345681743241125"
);

public static void main(String... args) throws InterruptedException {
    System.out.println("PI to " + PI.scale() + " digits is " + PI);
    System.out.println("PI^2 to " + PI.scale() + " digits is " + 
            PI.multiply(PI).setScale(PI.scale(), BigDecimal.ROUND_HALF_UP));
}

Ramène des souvenirs d'avoir à obtenir une valeur pour pi en fortran.

Puisqu'il n'y avait pas de bibliothèques de constantes, j'ai utilisé soit 4 * atan (1.) Ou acos (-1.).

Non, ils ne sont pas égaux, ils ont une présentation différente en mémoire.

En général, lorsque vous souhaitez comparer deux valeurs à virgule flottante, vous ne devez pas utiliser == (et si vous ne pouvez pas utiliser la terminaison 'égal à'). Vous devriez utiliser la comparaison avec epsilon.

double eps = 0.0000001;
if (Math.abs (Java_PI - Another_Pi) <= eps)
  System.out.println ("equals");
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top