2009-05-05 5 views
5

Je suis en train de coder plusieurs algorithmes de référence en Java et en C/C++. Certains de ces algorithmes utilisent π. Je voudrais que les deux implémentations de chaque algorithme produisent résultats identiques, sans arrondir différemment. Une façon de faire cela qui a toujours fonctionné est d'utiliser une constante pi personnalisée qui est exactement la même dans les deux langues, comme 3.14159. Cependant, il me semble idiot de définir pi quand il y a déjà des constantes de haute précision définies à la fois dans les bibliothèques Java et GCC.Est-ce que java.lang.Math.PI est égal à M_PI de GCC?

J'ai passé du temps à écrire des programmes de test rapides, à examiner la documentation de chaque bibliothèque et à lire les types à virgule flottante. Mais je n'ai pas été capable de me convaincre que java.lang.Math.PI (ou java.lang.StrictMath.PI) est, ou n'est pas, égal à M_PI dans math.h.

GCC 3.4.4 (Cygwin) math.h contient:

#define M_PI   3.14159265358979323846 
             ^^^^^ 

mais cette

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

produit

3.14159265358979311600 
       ^^^^^ 

qui suggère que les 5 derniers chiffres ne peuvent pas faire confiance .

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

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

et

public static final double PI 3.141592653589793d 

ce qui efface les discutables cinq derniers chiffres de la constante.

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

produit

3.14159265358979300000 
       ^^^^^ 

Si vous avez une certaine expertise dans 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 certainement pas égaux?

Répondre

2

Oui, ils sont égaux, et en utilisant les assurerai que GCC et les implémentations Java du même algorithme sont sur le même pied d'– au moins autant que l'aide d'un poignard et serait constante pi définie à la main;.

Une mise en garde, a laissé entendre par S. Lott, est que la mise en œuvre du CCG doit tenir M_PI dans un type de données double, et non long double, pour assurer l'équivalence. Java et GCC semblent utiliser la représentation décimale 64 bits de l'IEEE-754 pour leurs types de données double respectifs. La représentation par octet (MSB à LSB) de la valeur de la bibliothèque, exprimée en double, peut être obtenu 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)); 
    } 
} 

Courir les deux:

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

$ javac pi_bytes.java && java pi_bytes 
400921fb54442d18 

Le représentant sous-jacent les résentations de M_PI (double) et Math.PI sont identiques, jusqu'à leurs bits.

& dagger; – Comme noté par Steve Schnepp, la sortie de fonctions mathématiques telles que sin, cos, exp, etc. n'est pas garantie d'être identique, même si les entrées de ces calculs sont identiques au niveau des bits.

8

Ce que vous voulez faire est d'imprimer le modèle de bits bruts pour les valeurs PI et de les comparer.

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

Java 5 donne:

  • PI est 3.141592653589793
  • morceaux crus est 4614256656552045848
  • binaire est 100000000001001001000011111101101010100010001000010110100011000

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

+3

En C, vous pouvez faire double pi = M_PI; printf ("% lld \ n", pi); pour obtenir le même nombre entier 64bit: 4614256656552045848 –

+1

Merci Bruno, qui répond à peu près à la question originale. – JeeBee

-3

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

En général, lorsque vous voulez comparer 2 valeurs à virgule flottante, vous ne devez pas utiliser == (et si c'est le cas, vous ne pouvez pas utiliser termin 'equals'). Vous devriez utiliser la comparaison avec epsilon.

double eps = 0.0000001; 
if (Math.abs (Java_PI - Another_Pi) <= eps) 
    System.out.println ("equals"); 
1

double a seulement comme 52 bits de signficand donc je pense que vous donne seulement environ 15 base de 10 chiffres qui expliquerait pourquoi vous avez 5 zéros lorsque vous demandez 20 chiffres.

11

Notez ce qui suit.

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

Dans un nombre à virgule flottante 64 bits IEEE, il s'agit de tous les bits qui ne sont pas des signes ou des exposants. Le numéro #define M_PI comporte 21 chiffres; c'est environ 63 bits de précision, ce qui est bon pour une valeur à virgule flottante IEEE 80 bits.

Ce que je pense que vous voyez est la troncature ordinaire des bits dans la valeur M_PI.

+0

+1 Expliquez exactement ce problème lorsque vous effectuez des mesures de delta de temps extrêmement précises sur une longue période de temps. 15 chiffres est tout ce que vous obtenez avec le double 64 bits. – basszero

3

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 du calcul de virgule flottante sont parfois différents d'une architecture à l'autre (par exemple x86/PowerPC), d'un compilateur à un autre (pensez à GCC/MS C++) et même avec le même compilateur, mais différentes options de compilation. 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 beaucoup d'itérations, et beaucoup de nombreuses différences d'arrondi)

Cela rend assez difficile pour les jeux multi-joueurs multi-plateforme qui calculent chaque itération du gamestate de manière synchrone (chaque nœud ne reçoit que l'entrée, pas les structures de données réelles). Par conséquent si même dans la même langue (C/C++) les résultats peuvent être différents, à partir d'une machine virtuelle Java à un hôte natif, il peut également être différent.

Mise à jour:

Je ne peux pas trouver la source que je lis, mais je l'ai trouvé un paper by Sun sur la question.

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

+0

Peut-être que c'est un vrai problème dans le cas général, mais pas ici. Comme indiqué dans la question, "Une façon de faire cela qui a fonctionné de manière cohérente jusqu'à présent est d'utiliser une constante pi personnalisée qui est exactement la même dans les deux langues, comme 3.14159." –

+0

Ajout d'un lien de référence. –

1

Vous pouvez utiliser BigDecimal pour plus de précision comme:

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)); 
} 
+1

Euh, merci. Et pour GCC? –

-1

ramène des souvenirs d'avoir à obtenir une valeur pour pi Fortran.

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

Questions connexes