Pregunta

Considere el siguiente código C #:

double result1 = 1.0 + 1.1 + 1.2;
double result2 = 1.2 + 1.0 + 1.1;

if (result1 == result2)
{
    ...
}

resultado1 debe ser siempre igual result2 ¿verdad? La cosa es que no lo hace. resultado1 es 3,3 y result2 es 3.3000000000000003. La única diferencia es el orden de las constantes.

Sé que los dobles se implementan de tal manera que pueden producirse problemas de redondeo. Soy consciente de que puedo usar decimales en lugar si necesito una precisión absoluta. O que puedo usar Math.Round () si en mi declaración. Sólo soy un nerd que quiere entender lo que el compilador de C # está haciendo. ¿Puede alguien decirme?

Editar

Gracias a todos los que hasta ahora sugieren leer sobre la aritmética de coma flotante y / o hablado sobre la inexactitud inherente de cómo la CPU maneja dobles. Pero siento la idea central de mi pregunta sigue siendo sin respuesta. Que es mi culpa por no expresarlo correctamente. Déjame ponerlo de esta manera:

Romper el código anterior, esperaría que las siguientes operaciones para estar sucediendo:

double r1 = 1.1 + 1.2;
double r2 = 1.0 + r1
double r3 = 1.0 + 1.1
double r4 = 1.2 + r3

Vamos a suponer que cada una de las adiciones anteriores tenían un error de redondeo (e1..e4 numerado). Así R1 contiene redondeo de error E1, R2 incluye errores de redondeo e1 + e2, e3 R3 y R4 contiene contiene E3 + e4.

Ahora, no sé cómo es exactamente cómo ocurren los errores de redondeo, pero yo hubiera esperado e1 + e2 + e3 a la igualdad de e4. Es evidente que no es así, sino que de alguna manera parece mal a mí. Otra cosa es que cuando ejecuto el código anterior, no lo consigo algunos errores de redondeo. Eso es lo que me hace pensar que es el compilador de C # que está haciendo algo raro en lugar de la CPU.

Sé que estoy pidiendo mucho y tal vez lo mejor que se puede dar respuesta es ir a hacer un doctorado en el diseño de la CPU, pero yo sólo pensé en preguntar.

Editar 2

En cuanto a la IL de mi ejemplo de código original, es claro que es el compilador no la CPU que está haciendo esto:

.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
    .maxstack 1
    .locals init (
        [0] float64 result1,
        [1] float64 result2)
    L_0000: nop 
    L_0001: ldc.r8 3.3
    L_000a: stloc.0 
    L_000b: ldc.r8 3.3000000000000003
    L_0014: stloc.1 
    L_0015: ret 
}

El compilador es sumar los números para mí!

¿Fue útil?

Solución

  

Me habría esperado e1 + e2 + e3 a la igualdad de e4.

Eso no es del todo a diferencia de esperar

 floor( 5/3 ) + floor( 2/3 + 1 )

a igual

 floor( 5/3 + 2/3 ) + floor( 1 )

excepto que estás multiplicando por 2 ^ 53 antes de tomar la palabra.

El uso de coma flotante de precisión de 12 bits y el truncamiento con sus valores:

1.0            =  1.00000000000
1.1            =  1.00011001100
1.2            =  1.00110011001

1.0 + 1.1      = 10.00011001100 // extended during sum
r1 = 1.0 + 1.1 = 10.0001100110  // truncated to 12 bit
r1  + 1.2      = 11.01001100101 // extended during sum
r2 = r1  + 1.2 = 11.0100110010  // truncated to 12 bit

1.1 + 1.2      = 10.01001100110 // extended during sum
r3 = 1.1 + 1.2 = 10.0100110011  // truncated to 12 bit
r3 + 1.0       = 11.01001100110 // extended during sum
r4 = r3  + 1.0 = 11.0100110011  // truncated to 12 bit

Así que cambiar el orden de las operaciones / truncamientos hace que el error de cambiar, y R4! = R2. Si se agrega 1.1 y 1.2 en este sistema, el último bit lleva, por lo que no se pierde en el truncamiento. Si se agrega 1,0 a 1,1, el último bit de 1.1 se pierde y por lo que el resultado no es el mismo.

En una ordenación, el redondeo (por truncamiento) elimina un 1 final.

En el otro pedido, el redondeo elimina una 0 de arrastre en ambas ocasiones.

Uno no es igual a cero; por lo que los errores no son los mismos.

Dobles tiene muchos más bits de precisión, y C #, probablemente utiliza el redondeo en lugar de truncamiento, pero esperemos que este modelo simple que muestra diferentes errores pueden ocurrir con diferentes ordenamientos de los mismos valores.

La diferencia entre FP y las matemáticas es que + es la abreviatura de 'añadir a continuación redonda' en lugar de simplemente añadir.

Otros consejos

El compilador de C # no está haciendo nada. La CPU es.

Si usted tiene una en un registro de la CPU, y a continuación, añadir B, el resultado almacenado en dicho registro es A + B, aproximado a la precisión flotante utilizado

Si a continuación, añadir C, el error se suma. Esta adición error no es una operación transitivo, por lo tanto la diferencia final.

el artículo clásico (Lo que todo científico de la computación debe saber acerca de punto flotante aritmética) sobre el tema. Este tipo de cosas es lo que sucede con la aritmética de punto flotante. Se necesita un informático para decirle que 1/3 + 1/3 + 1/3 is'nt igual a 1 ...

Orden de operaciones de punto flotante es importante. No responde directamente a su pregunta, pero siempre debe ser la comparación de números de punto flotante cuidadosas. Es usual incluir una tolerancia:

double epsilon = 0.0000001;
if (abs(result1 - result2) <= epsilon)
{
    ...
}

Esto puede ser de interés: Lo que todo científico debe saber sobre el ordenador flotante aritmética de punto

  

resultado1 debe ser siempre igual result2   ¿verdad?

incorrecto . Eso es cierto en las matemáticas, pero no está en de coma flotante aritmética .

Usted tendrá que leer algunos Análisis Numérico imprimación .

¿Por qué los errores no son los mismos en función de orden se puede explicar con un ejemplo diferente.

Digamos que para los números por debajo de 10, puede almacenar todos los números, por lo que puede almacenar 1, 2, 3, y así sucesivamente hasta e incluyendo 10, pero después de 10, sólo puede almacenar cada segundo número, debido a la pérdida interna de precisión, en otras palabras, sólo puede almacenar 10, 12, 14, etc.

Ahora, con ese ejemplo, verá por qué la siguiente produce resultados diferentes:

1 + 1 + 1 + 10 = 12 (or 14, depending on rounding)
10 + 1 + 1 + 1 = 10

El problema con los números de punto flotante es que no se pueden representar con precisión, y el error no va siempre de la misma manera, por lo que el orden tendrán importancia.

Por ejemplo, 3,00000000003 + 3,00000000003 podría terminar siendo 6,00000000005 (aviso no 6 al final), pero 3.00000000003 + 2,99999999997 podría terminar siendo 6,00000000001, y con eso:

step 1: 3.00000000003 + 3.00000000003 = 6.00000000005
step 2: 6.00000000005 + 2.99999999997 = 9.00000000002

pero, cambiar el orden:

step 1: 3.00000000003 + 2.99999999997 = 6.00000000001
step 2: 6.00000000001 + 3.00000000003 = 9.00000000004

Por lo tanto, tendrá importancia.

Ahora, por supuesto, es posible que la suerte de que los ejemplos anteriores se equilibran entre sí, porque la primera se muevan hacia arriba por .xxx1 y el otro por .xxx1, que le da .xxx3 en ambos, pero no hay garantía.

En realidad se está utilizando no los mismos valores, porque los resultados intermedios son diferentes:

double result1 = 2.1 + 1.2;
double result2 = 2.2 + 1.1;

Debido a que los dobles no pueden representar valores decimales con exactitud a obtener resultados diferentes.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top