2012-02-10 30 views
23

En .NET, ¿por qué System.Math.Round(1.035, 2, MidpointRounding.AwayFromZero) produce 1.03 en vez de 1.04? Siento que la respuesta a mi pregunta se encuentra en la sección titulada "Nota para personas que llaman" al http://msdn.microsoft.com/en-us/library/ef48waz8.aspx, pero no puedo entender bien la explicación.¿Por qué System.MidpointRounding.AwayFromZero no se redondea en esta instancia?

+0

Curiosamente, llamar a este método con un valor de 1.135 declaraciones de 1.14. –

+10

La "nota" es esencialmente que es un número de base 2. No puede representar ciertos valores * con precisión. * Escribe 1.035, la representación interna puede ser 1.034999999982 o lo que sea. Si le interesa la representación exacta de los dígitos con un número determinado de decimales, quizás System.Decimal sea el tipo para usted. Particularmente si se trata de valores financieros. –

+1

@KyleTrauberman eso es porque con 1.135 tienes suerte, la aproximación aterriza un poco más alto que 1.135, en comparación con el más bajo como en el PO. –

Respuesta

26

Su sospecha es exactamente correcto. Los números con porción fraccionaria, cuando se expresan como literales en .NET, son por defecto doubles. Un doble (como un flotador) es una aproximación de un valor decimal, no un valor decimal preciso. Es el valor más cercano que se puede expresar en base-2 (binario). En este caso, la aproximación es siempre tan vagamente en el lado pequeño de 1.035. Si usted lo escribe utilizando un decimal explícito funciona como se espera:

Console.WriteLine(Math.Round(1.035m, 2, MidpointRounding.AwayFromZero)); 
Console.ReadKey(); 

Para entender por qué los flotadores dobles y funcionan de la manera que lo hacen, imagina que representa el número 1/3 en decimal (o binario, que sufre de la el mismo problema). No se puede traducir a .3333333 ...., lo que significa que representarlo con precisión requeriría una cantidad infinita de memoria.

Las computadoras obvian esto con aproximaciones. Explicaría exactamente cómo, pero probablemente me equivocaría. Puede leer todos los detalles aquí, sin embargo: http://en.wikipedia.org/wiki/IEEE_754-1985

+0

Nota, el valor predeterminado es * doble. * 1.0 es implícitamente un doble, aunque puede ser explícitamente con 1d o 1.0d. El sufijo 'f' es requerido para los literales float. –

+0

Reparado, gracias Anthony –

+0

@ChrisShain doble es de hecho un valor preciso; es solo un valor binario preciso, que puede o no ser una aproximación al valor decimal preciso indicado por el literal. Por ejemplo, el valor binario '0.25d' es exactamente igual al valor decimal 0.25. Además creo que quisiste decir "imagina que representa el número 1/3 en decimal" porque 0.33333 ... es la representación decimal, no binaria. La representación binaria es más como 0.0101010101 ... – phoog

1

Supongo que internamente 1.035 no se puede representar en binario exactamente como 1.035 y es probable que (por debajo) 1.0349999999999999, por lo que se redondea hacia abajo.

Sólo una suposición.

3

Creo que el ejemplo al que se refiere es un problema diferente; por lo que entiendo están diciendo que 0.1 no está almacenado, en flotación, como exactamente 0.1, en realidad está un poco apagado debido a cómo se almacenan los flotadores en binario. Como tal, supongamos que en realidad se parece más a 0.0999999999999 (o similar), algo muy, muy poco menos de 0.1 - tan levemente que no tiende a hacer mucha diferencia. Bueno, no, están diciendo: una diferencia notable sería que agregar esto a su número y redondear en realidad parece ir en la dirección equivocada porque, aunque los números son extremadamente cercanos, se considera "menor que" el .5 para redondeo .

Si no he entendido bien esa página, espero que alguien me corrige :)

no veo cómo se relaciona con su llamada, sin embargo, porque estás siendo más explícito. Tal vez solo esté almacenando su número de una manera similar.

+0

Eso es exactamente correcto. –

+0

+1 para "debido a cómo se almacenan los flotadores en binario". – phoog

6

Me'ts debido a que la representación binaria de 1.035 más cerca de 1.03 a 1.04

Para obtener mejores resultados hacerlo de esta manera -

decimal result = decimal.Round(1.035m, 2, MidpointRounding.AwayFromZero); 
7

La representación binaria de 1.035d es 0x3FF08F5C28F5C28F, que de hecho es 1.03499999999999992006394222699E0, por lo System.Math.Round (1.035, 2, MidpointRounding.AwayFromZero) dió 1,03 en lugar de 1,04 así que es correcto

Sin embargo, la representación binaria de 4.005d es 0x4010051EB851EB85, que es 4,00499999999999989341858963598, por lo System.Math.Round (4.005, 2, MidpointRounding.AwayFromZero) debe producir 4,00, pero dió 4,01 que está mal (o un inteligente ' fijar'). Si lo compruebas en MS SQL, selecciona ROUND (CAST (4.005 AS float), 2), es 4.00 No entiendo por qué .NET aplica esta 'solución inteligente' que empeora las cosas.

Puede comprobar la representación binaria de un doble en: http://www.binaryconvert.com/convert_double.html

Cuestiones relacionadas