Domanda

Sto codificando diversi algoritmi di riferimento sia in Java che in C / C ++. Alcuni di questi algoritmi usano p. Vorrei che le due implementazioni di ciascun algoritmo producessero risultati identici , senza arrotondare diversamente. Un modo per farlo che ha funzionato in modo coerente finora è usare una costante pi definita in modo identico in entrambe le lingue, come 3.14159. Tuttavia, mi sembra sciocco definire pi quando ci sono già costanti di alta precisione definite nelle librerie Java e GCC.

Ho trascorso un po 'di tempo a scrivere programmi di test rapidi, a cercare documentazione per ciascuna libreria e a leggere su tipi a virgola mobile. Ma non sono stato in grado di convincermi che java.lang.Math.PI (o java.lang.StrictMath.PI) sia o non sia 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 possono essere considerate attendibili.

Nel frattempo, Javadocs afferma che java.lang.Math.PI è:

  

Il valore double che è più vicino di   qualsiasi altro rispetto a pi , il rapporto tra   circonferenza di un cerchio al suo   di diametro.

e

public static final double PI  3.141592653589793d

che omette le ultime cinque cifre discutibili dalla costante.

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

produce

3.14159265358979300000
                 ^^^^^

Se hai qualche esperienza nei tipi di dati in virgola mobile, puoi convincermi che queste costanti di libreria sono esattamente uguali? O che non sono assolutamente uguali?

È stato utile?

Soluzione 4

Sì, sono uguali e il loro utilizzo assicurerà che le implementazioni GCC e Java dello stesso algoritmo siano sullo stesso piano & # 8211; almeno quanto l'utilizzo di una costante pi definita a mano & # 8224; .

Un avvertimento, suggerito da S. Lott , è che l'implementazione GCC deve contenere M_PI in un tipo di dati double , e non double long , 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 bytewise (da MSB a LSB) del valore della libreria, espressa come double , può essere ottenuta 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 ) );
   }
}

Esecuzione di entrambi:

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

$ javac pi_bytes.java && java pi_bytes
400921fb54442d18

Le rappresentazioni sottostanti di M_PI (come double ) e Math.PI sono identiche, fino ai loro bit.

& # 8224; & # 8211; Come notato da Steve Schnepp , l'output di funzioni matematiche come sin, cos, exp, ecc. Non è garantito identico, anche se gli input per questi calcoli sono identici per bit.

Altri suggerimenti

Nota quanto segue.

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

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

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

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

Quello che vuoi fare è stampare il modello di bit non elaborato per i valori PI e confrontarli.

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

Java 5 fornisce:

  • PI è 3.141592653589793
  • I bit non elaborati sono 4614256656552045848
  • Binario è 100000000001001001000011111101101010100010001000010110100011000

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

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

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

Ciò rende abbastanza difficile i giochi multiplayer 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 nella stessa lingua (C / C ++) possono essere diversi, da una VM Java a un host nativo potrebbe anche essere diversa.

Aggiornamento:

Non riesco a trovare la fonte che ho letto, ma ho trovato un di Sun sulla questione.

Come hai risposto tu stesso, java.lang.Math.PI e M_PI di GCC & # 8217; s 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 è il output del calcolo che non è necessariamente lo stesso.

un doppio ha solo 52 bit di segno, quindi penso che ti dia solo circa 15 cifre di base 10 che spiegherebbero perché hai 5 zero quando chiedi 20 cifre.

Puoi usare 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));
}

Riporta i ricordi di dover ottenere un valore per pi in fortran.

Dato che non c'erano librerie di costanti, ho usato entrambe 4 * atan (1.) O acos (-1.).

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 il termine 'uguale'). Dovresti usare il confronto con epsilon.

double eps = 0.0000001;
if (Math.abs (Java_PI - Another_Pi) <= eps)
  System.out.println ("equals");
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top