2009-05-05 16 views
5

Sto codificando diversi algoritmi di riferimento sia in Java che in C/C++. Alcuni di questi algoritmi utilizzano π. Vorrei che le due implementazioni di ciascun algoritmo producessero i risultati identici, senza arrotondamenti in modo diverso. Un modo per fare ciò che ha funzionato in modo coerente finora è utilizzare una costante pi definita dall'utente che è esattamente la stessa in entrambe le lingue, come 3,14159. Tuttavia, mi sembra sciocco definire pi quando esistono già costanti ad alta precisione definite nelle librerie Java e GCC.java.lang.Math.PI è uguale a M_PI di GCC?

Ho trascorso un po 'di tempo a scrivere programmi di test rapidi, a consultare la documentazione per ogni libreria e a leggere i tipi in virgola mobile. Ma non sono stato in grado di convincermi che java.lang.Math.PI (o java.lang.StrictMath.PI) è, o non è, uguale a M_PI in math.h.

GCC 3.4.4 (cygwin) math.h contiene:

#define M_PI   3.14159265358979323846 
             ^^^^^ 

ma questo

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

produce

3.14159265358979311600 
       ^^^^^ 

che suggerisce che le ultime 5 cifre non sono affidabili .

frattempo, Javadocs dire che java.lang.Math.PI è:

Il valore double che è più vicino di qualsiasi altro per pi, il rapporto tra la circonferenza di un cerchio alla sua diametro.

e

public static final double PI 3.141592653589793d 

che omette gli discutibili ultime cinque cifre della costante.

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

produce

3.14159265358979300000 
       ^^^^^ 

Se avete qualche esperienza in tipi di dati a virgola mobile, si può convincermi che queste costanti di libreria sono esattamente uguali? O che non sono assolutamente uguali?

risposta

2

Sì, sono uguali, e il loro utilizzo assicurerà che GCC e implementazioni Java dello stesso algoritmo sono sullo stesso piano – almeno tanto quanto con un definito mano pi costante farebbe & pugnale;.

Un avvertimento, accennato da S. Lott, è che l'attuazione GCC deve tenere M_PI in un tipo di dati double, e non long double, per garantire l'equivalenza. Sia Java che GCC sembrano utilizzare la rappresentazione decimale a 64 bit di IEEE-754 per i rispettivi tipi di dati double. La rappresentazione a byte (MSB a LSB) del valore biblioteca, espresso come double, può essere ottenuto come segue (grazie a 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)); 
    } 
} 

corsa sia:

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

$ javac pi_bytes.java && java pi_bytes 
400921fb54442d18 

Il rappresentante sottostante i resentations di M_PI (come double) e Math.PI sono identici, fino ai loro bit.

e pugnale; – Come notato da Steve Schnepp, l'output delle funzioni matematiche come sin, cos, exp, ecc. Non è garantito per essere identico, anche se gli input per questi calcoli sono identici per bit.

8

Quello che si vuole fare è stampare il modello di bit grezzi per i valori PI e confrontarli.

In Java utilizzare il metodo http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Double.html#doubleToRawLongBits(double) per ottenere il valore lungo che si dovrebbe stampare come binario.

Java 5 dà:

  • PI è 3.141592653589793
  • bit RAW è 4614256656552045848
  • binaria è 100000000001001001000011111101101010100010001000010110100011000

In C, si può fare double pi = M_PI; printf("%lld\n", pi); per ottenere lo stesso numero intero a 64 bit: 4614256656552045848 (grazie Bruno).

+3

In C, è possibile eseguire il doppio pi = M_PI; printf ("% lld \ n", pi); per ottenere lo stesso numero intero a 64 bit: 4614256656552045848 –

+1

Grazie Bruno, questo praticamente risponde alla domanda originale. – JeeBee

-3

No, non sono uguali, hanno una presentazione diversa in memoria.

In generale quando si desidera confrontare 2 valori in virgola mobile non è necessario utilizzare == (e in tal caso non è possibile operare con termin 'equals'). Dovresti usare il confronto con epsilon.

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

una doppia ha solo come 52 bit di signficand quindi penso che solo gli dà circa 15 base di 10 cifre che spiegherebbe perché avete 5 zeri quando si chiede 20 cifre.

11

Tenere presente quanto segue.

I due numeri sono uguali a 16 posizioni decimali. Sono quasi 48 bit che sono uguali.

In un numero a virgola mobile IEEE a 64 bit, sono tutti i bit che non sono né segni né esponenti.

Il #define M_PI ha 21 cifre; questo è circa 63 bit di precisione, il che è positivo per un valore in virgola mobile IEEE a 80 bit.

Quello che penso tu stia vedendo è il normale troncamento dei bit nel valore M_PI.

+0

+1 Si è imbattuto esattamente in questo problema quando si effettuano misure estreme delta del tempo di precisione per un lungo periodo di tempo. 15 cifre è tutto ciò che ottieni con 64-bit double. – basszero

3

Sarebbe molto difficile calcolare lo stesso valore, anche se i valori iniziali sono gli stessi.

virgola mobile risultati del calcolo sono talvolta differenti da un'architettura all'altra (si pensi x86/PowerPC per esempio), da un compilatore all'altro (si pensi GCC/MS C++) e anche con lo stesso compilatore, ma diverse opzioni di compilazione. Non sempre, ma a volte (di solito durante l'arrotondamento). Solitamente quel tanto che basta per passare inosservato il problema fino a tardi (pensa dopo molte iterazioni e molte differenze di arrotondamento)

Questo rende abbastanza difficile per i giochi multipiattaforma multipiattaforma che calcolano ogni iterazione del gamestate in modo sincrono (ogni nodo riceve solo l'input, non le strutture dati effettive).

Pertanto, anche se i risultati dello stesso linguaggio (C/C++) possono essere diversi, da una VM Java a un host nativo potrebbe anche essere diverso.

Aggiornamento:

Non riesco a trovare la fonte che ho letto, ma ho trovato un paper by Sun in materia.

Come se avessi risposto tu stesso, java.lang.Math.PI e GCC's M_PI possono essere gestiti per avere lo stesso valore. Il diavolo si nasconde nell'uso di questi valori. IEEE non specifica l'output delle funzioni matematiche (sin, cos, exp, ...). Pertanto è l'output del calcolo che non è necessariamente lo stesso.

+0

Forse questo è un vero problema nel caso generale, ma non qui. Come notato nella domanda, "Un modo per fare ciò che ha funzionato in modo coerente finora è usare una costante pi personalizzata che è esattamente la stessa in entrambe le lingue, come 3.14159." –

+0

Aggiunto un link di riferimento. –

1

È possibile utilizzare BigDecimal per una maggiore precisione come:

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

Ehm, grazie. E per GCC? –

-1

riporta i ricordi di dover ottenere un valore per pi greco in FORTRAN.

Poiché non c'erano librerie di costanti, o ho usato 4 * atan (1.) O acos (-1.).