2009-05-05 16 views
5

Estoy codificando varios algoritmos de referencia tanto en Java como en C/C++. Algunos de estos algoritmos usan π. Me gustaría que las dos implementaciones de cada algoritmo produzcan resultados idénticos, sin redondeo de forma diferente. Una forma de hacer esto que ha funcionado consistentemente hasta el momento es usar una constante personalizada pi que sea exactamente la misma en ambos idiomas, como 3.14159. Sin embargo, me parece tonto definir pi cuando ya hay constantes de alta precisión definidas en las bibliotecas de Java y GCC.¿Es java.lang.Math.PI igual a M_PI de GCC?

Pasé algún tiempo escribiendo programas de prueba rápida, mirando la documentación de cada biblioteca y leyendo sobre tipos de coma flotante. Pero no he podido convencerme de que java.lang.Math.PI (o java.lang.StrictMath.PI) es, o no es, igual a M_PI en math.h.

GCC 3.4.4 (cygwin) math.h contiene:

#define M_PI   3.14159265358979323846 
             ^^^^^ 

pero esto

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

produce

3.14159265358979311600 
       ^^^^^ 

lo que sugiere que los 5 últimos dígitos no se puede confiar .

Mientras tanto, Javadocs decir que java.lang.Math.PI es:

El valor double que está más cerca que cualquier otra a pi, la relación de la circunferencia de un círculo a su diámetro.

y

public static final double PI 3.141592653589793d 

que omite las cuestionables últimos cinco dígitos de la constante.

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

produce

3.14159265358979300000 
       ^^^^^ 

Si usted tiene alguna experiencia en los tipos de datos de punto flotante, se puede convencerme de que estas constantes de la biblioteca son exactamente iguales? O que definitivamente no son iguales?

Respuesta

2

Sí, ellos son iguales, y su uso se asegurará de que GCC y las implementaciones de Java del mismo algoritmo están en el mismo plano – al menos tanto como el uso de un pi constante definida a mano haría y daga;.

Una advertencia, insinuado por S. Lott, es que la aplicación GCC debe mantener M_PI en un tipo de datos double, y no long double, para asegurar la equivalencia. Tanto Java como GCC parecen usar la representación decimal de 64 bits de IEEE-754 para sus respectivos tipos de datos double. La representación byte a byte (MSB a LSB) del valor biblioteca, expresado como double, se pueden obtener de la siguiente manera (gracias 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)); 
    } 
} 

ejecutan al mismo tiempo:

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

$ javac pi_bytes.java && java pi_bytes 
400921fb54442d18 

El representante subyacente las representaciones de M_PI (como double) y Math.PI son idénticas, hasta sus bits.

& dagger; – Como se indica en Steve Schnepp, la salida de funciones matemáticas como sin, cos, exp, etc. no se garantiza que sea idéntica, incluso si las entradas a esos cálculos son idénticas a nivel de bit.

8

Lo que quiere hacer es imprimir el patrón de bits sin procesar para los valores PI y compararlos.

En Java, utilice el método http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Double.html#doubleToRawLongBits(double) para obtener el valor largo que debe imprimir como binario.

Java 5 da:

  • PI es 3.141592653589793
  • bits de primas es 4614256656552045848
  • binario es 100000000001001001000011111101101010100010001000010110100011000

En C, se puede hacer double pi = M_PI; printf("%lld\n", pi); para obtener el mismo número entero de 64 bits: 4614256656552045848 (gracias Bruno).

+3

En C, puede hacer doble pi = M_PI; printf ("% lld \ n", pi); para obtener el mismo número entero de 64 bits: 4614256656552045848 –

+1

Gracias Bruno, eso más o menos responde la pregunta original. – JeeBee

-3

No, no son iguales, tienen una presentación diferente en la memoria.

En general, cuando se desea comparar 2 valores de coma flotante no debe usar == (y si es así no se puede operar con termin 'es igual a'). Deberías usar la comparación con épsilon.

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

un doble sólo tiene como 52 bits de signficand así que creo que sólo le da alrededor de 15 bases 10 dígitos lo que explicaría por qué tiene 5 ceros cuando se pide 20 dígitos.

11

Tenga en cuenta lo siguiente.

Los dos números son iguales a 16 cifras decimales. Eso es casi 48 bits que son lo mismo.

En un número de coma flotante IEEE de 64 bits, eso es todos los bits son los que no son signos o exponentes.

El #define M_PI tiene 21 dígitos; eso es aproximadamente 63 bits de precisión, lo cual es bueno para un valor de punto flotante IEEE de 80 bits.

Lo que creo que se está viendo es el truncamiento ordinaria de los bits en el valor M_PI.

+0

+1 Se encontró exactamente con este problema al realizar mediciones delta de tiempo extremadamente precisas durante un período de tiempo grande. 15 dígitos es todo lo que obtienes con el doble de 64 bits. – basszero

3

Sería muy difícil calcular el mismo valor, , incluso si los valores iniciales son los mismos.

Los resultados del cálculo de coma flotante son a veces diferentes de una arquitectura a otra (piense x86/PowerPC por ejemplo), de un compilador a otro (piense en GCC/MS C++) e incluso con el mismo compilador, pero con diferentes opciones de compilación. No siempre, pero a veces (generalmente al redondear). Por lo general solo lo suficiente para que el problema pase desapercibido hasta muy tarde (piense en muchas iteraciones y muchas diferencias de redondeo)

Esto hace que sea bastante difícil para juegos multiplataforma para múltiples jugadores calcular cada iteración del sistema de juego de forma sincrónica (cada nodo solo recibe la entrada, no las estructuras de datos reales).

Por lo tanto, si incluso en el mismo idioma (C/C++) los resultados pueden ser diferentes, desde una máquina virtual Java en un huésped nativo también puede ser diferente.

Actualización:

no puedo encontrar la fuente leí, pero me encontré con un paper by Sun en la materia.

Como usted mismo, java.lang.Math.PI y GCC's M_PI pueden administrarse para tener el mismo valor. El diablo se esconde en el uso de estos valores. IEEE no especifica el resultado de las funciones matemáticas (sin, cos, exp, ...). Por lo tanto, es la salida del cálculo que no es necesariamente la misma.

+0

Tal vez este es un verdadero problema en el caso general, pero no aquí. Como se señala en la pregunta, "Una forma de hacer esto que ha funcionado consistentemente hasta el momento es usar una constante pi personalizada que sea exactamente la misma en ambos idiomas, como 3.14159". –

+0

Agregó un enlace de referencia. –

1

Usted puede usar BigDecimal para más exactitud como:

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

Um, gracias. Y para GCC? –

-1

trae recuerdos de tener que obtener un valor para pi en FORTRAN.

Como no había bibliotecas de constantes, utilicé 4 * atan (1.) O acos (-1.).