Pregunta

Estoy codificando varios algoritmos de referencia tanto en Java como en C / C ++. Algunos de estos algoritmos utilizan & # 960 ;. Me gustaría que las dos implementaciones de cada algoritmo produzcan resultados idénticos , sin redondear de manera diferente. Una forma de hacer esto que hasta ahora ha funcionado consistentemente es usar una constante pi definida por el usuario que sea exactamente igual en ambos idiomas, como 3.14159. Sin embargo, me parece una tontería definir pi cuando ya hay constantes de alta precisión definidas en las bibliotecas de Java y GCC.

He pasado algún tiempo escribiendo programas de prueba rápida, revisando la documentación de cada biblioteca y leyendo sobre tipos de punto flotante. Pero no he podido convencerme de que java.lang.Math.PI (o java.lang.StrictMath.PI) es, o no, 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 no se puede confiar en los últimos 5 dígitos.

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

  

El valor double que está más cerca que   cualquier otro a pi , la proporción de   circunferencia de un circulo a su   diámetro.

y

public static final double PI  3.141592653589793d

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

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

produce

3.14159265358979300000
                 ^^^^^

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

¿Fue útil?

Solución 4

Sí, son iguales, y su uso asegurará que las implementaciones de GCC y Java del mismo algoritmo estén en la misma posición & # 8211; al menos tanto como usar una constante pi definida a mano sería & # 8224; .

Una advertencia, sugerida por S. Lott , es que la implementación de GCC debe contener M_PI en un tipo de datos doble , y no long double , para asegurar la equivalencia. Parece que tanto Java como GCC usan la representación decimal de 64 bits de IEEE-754 para sus respectivos tipos de datos doble . La representación en bytes (MSB a LSB) del valor de la biblioteca, expresada como double , se puede 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 ) );
   }
}

Ejecutando ambos:

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

$ javac pi_bytes.java && java pi_bytes
400921fb54442d18

Las representaciones subyacentes de M_PI (como double ) y Math.PI son idénticas, hasta sus bits.

& # 8224; & # 8211; Como se señala en Steve Schnepp , no se garantiza que la salida de funciones matemáticas como sin, cos, exp, etc. idénticas, incluso si las entradas a esos cálculos son idénticas a nivel de bits.

Otros consejos

Tenga en cuenta lo siguiente.

Los dos números son iguales a 16 decimales. Eso es casi 48 bits que son los mismos.

En un número de punto flotante IEEE de 64 bits, esos son todos los bits que no son signos ni exponentes.

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

Lo que creo que estás viendo es un truncamiento ordinario de los bits en el valor M_PI .

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

En Java use 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
  • Los bits sin procesar son 4614256656552045848
  • El binario es 100000000001001001000011111010101010100010001000010110100011000

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

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

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

Esto hace que sea bastante difícil para los juegos multiplataforma multiplataforma que computan cada iteración del juego sincrónicamente (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 a un host nativo, también puede ser diferente.

Actualización:

No puedo encontrar la fuente que leí, pero encontré un documento por Sun al respecto.

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

un doble solo tiene 52 bits de signo, por lo que creo que solo le da alrededor de 15 dígitos de base 10, lo que explicaría por qué tiene 5 ceros cuando solicita 20 dígitos.

Puedes usar BigDecimal para mayor precisión 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));
}

Trae recuerdos de tener que obtener un valor para pi en fortran.

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

No, no son iguales, tienen diferentes presentaciones en la memoria.

En general, cuando desea comparar 2 valores de punto flotante no debe usar == (y si no puede operar con el término '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");
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top