2012-04-17 17 views
18

¿Por qué es esto cierto? Parece que Java produce un resultado con una pequeña discrepancia al multiplicar dos flotantes en comparación con C e incluso el método Java Math.pow.Punto flotante Java vs C: "x * x" difiere de "pow (x, 2)"?

Java:

float a = 0.88276923; 

double b = a * a; // b becomes 0.779281497001648 <---- what??? 
b = Math.pow(a,2); // b becomes 0.7792815081874238 

C:

float a = 0.88276923; 

double b = a * a; // b becomes 0.7792815081874238 
pow(a,2);   // b becomes 0.7792815081874238 

Actualización: Por el comentario de Ed S., también he encontrado que el comportamiento C cambia dependiendo del compilador. Al usar gcc, parece coincidir con el comportamiento de Java. El uso de Visual Studio (dependiendo de su plataforma de destino) puede producir los resultados que se muestran arriba o los que se ven en Java. Ugh.

+2

http://floating-point-gui.de/ –

+3

Ah, la aritmética de coma flotante. Un bastión puro de precisión y fiabilidad. – Perception

+1

Soy consciente de que las carrozas no son precisas. Sin embargo, esperaría que su imprecisión fuera consistente. – mark

Respuesta

16

Como pst y trutheality ya han señalado sabiamente, C es la promoción de la float a un double antes de la multiplicación. En realidad, se promocionan a un valor de precisión ampliado de 80 bits cuando se insertan en la pila. Aquí está la salida del ensamblador (VS2005 x86 C89)

double b = a * a; 
00411397 fld   dword ptr [a] 
0041139A fmul  dword ptr [a] 
0041139D fstp  qword ptr [b] 

La Instrucción FLD

La instrucción FLD carga un 32 bit, 64 bit, o el valor de punto flotante de 80 bits en la pila. Esta instrucción convierte los operandos de 32 y 64 bits en un valor de precisión ampliado de 80 bits antes de empujar el valor en la pila de coma flotante.


Curiosamente, si construyo para apuntar x64, la instrucción movss se utiliza y se obtiene un valor de 0.779281497001648 como el resultado, es decir, lo que está viendo en su ejemplo de Java. Darle una oportunidad.

+2

+1. Ahora, en el estándar C (¡y no olvides especificar qué borrador!) Hay una declaración general que se puede interpretar como un comportamiento definido por la implementación en dos ... ;-) –

+2

@pst: Ojalá tuviera el momento de mirar, pero .... es la noche de la fecha: D –

+0

@pst: Intenté apuntar a x64 por curiosidad y se ve el comportamiento de java ya que la instrucción 'fld' ya no se usa (al menos en mi configuración). –

10

¿Qué Java para

double b = a * a; 

es multiplicar a * a como (32-bit) float primero, y convierte el resultado a un (64-bit) double al asignar a b.

b = Math.pow(a,2); 

Convierte a a un (64-bit) double primero (ya que los parámetros para Math.pow son double, double) y luego los cuadrados de TI.

Lo que resulta desconcertante (para mí) es la razón por C parece arrojar los a 's de double por primera vez en

double b = a * a; 

Es que en la norma?

Editar: Recuerdo vagamente sobre C que no requieren una implementación particular (en términos de cuántos bits se utilizan para los números) ... es que lo que está pasando aquí? ¿Son sus float s 64 bits? (En Java, un float es siempre de 32 bits y un double siempre es de 64 bits).

Edit: Tanto la respuesta de Ed S. como el comentario de la marca de que diferentes compiladores arrojan resultados diferentes indican que los resultados de C son específicos de la implementación y la arquitectura.

+0

. Me pregunto si también se produce alguna evaluación (optimización) en tiempo de compilación. –

+0

(Aunque dudaría de que 'sizeof (float) == sizeof (double)' en una máquina/compilador moderno ;-) –

+0

En mi máquina se promocionan cuando se empujan a la pila. Ver: [La instrucción FLD] (http://webster.cs.ucr.edu/AoA/Windows/HTML/RealArithmetica2.html) –