viernes, 29 de junio de 2012

Cuidado con los redondeos porque puedes perder algún céntimo

Hoy voy a hablar sobre uno de esos fallos que te puede volver loco toda una mañana e incluso hacerte perder los papeles, sobre todo al final cuando te das cuenta del fallo.

En esta ocasión la cosa va de redondeos de importes de facturas así que lo primero que haremos es buscar como se redondea los valores en euors. Según la sección 3, artículo 11 de la ley 46/1998, de 17 de diciembre (consultar aquí), sobre la introducción del euro

Uno. En los importes monetarios que se hayan de abonar o contabilizar, cuando se lleve a cabo una operación de redondeo después de una conversión a la unidad euro, deberán redondearse por exceso o por defecto al céntimo más próximo. Los importes monetarios que se hayan de abonar o contabilizar y se conviertan a la unidad monetaria peseta deberán redondearse por exceso o por defecto a la peseta más próxima. En caso de que al aplicar el tipo de conversión se obtenga una cantidad cuya última cifra sea exactamente la mitad de un céntimo o de una peseta, el redondeo se efectuará a la cifra superior.

O sea que debemos redondear a dos decimales al céntimo más próximo y en caso que el tercer decimal sea 5 debemos redondear al alza. En base a esto haremos unas pruebas para ver el resultado
decimal d1;
decimal d2;

d1 = 123.472m;
d2 = Math.Round(d1, 2);
Print(d1, d2);

d1 = 123.478m;
d2 = Math.Round(d1, 2);
Print(d1, d2);

d1 = 123.475m;
d2 = Math.Round(d1, 2);
Print(d1, d2);
La salida de este programa es la siguiente
Valor: 123,472
Valor redondeado: 123,47
---
Valor: 123,478
Valor redondeado: 123,48
---
Valor: 123,475
Valor redondeado: 123,48
Como vemos todo funciona correctamente pero vamos a usar el valor 123,465 que según la ley debería ser redondeado a 123,47.
d1 = 123.465m;
d2 = Math.Round(d1, 2);
Print(d1, d2);
Y la salida sería
Valor: 123,465
Valor redondeado: 123,46
Si nos fijamos, vemos que el valor obtenido no es el que esperamos, y con este error nos tropezamos el otro día. Estabamos convencidos que el código estaba bien, ya que la función Math.Round hace el redondeando al alza en caso de ser un cinco. Tras mucho darle vueltas al asunto y algun que otro "me cago en todo", nos dio por leernos con detalle la ayuda del método Round.

El valor devuelto por el método Math.Round es "el número más próximo a d con una precisión igual a decimals. Si d se encuentra entre dos números, uno de los cuales es par y el otro es impar, se devuelve el número par. Si la precisión de d es menor que decimals, se devuelve d sin modificar."

Vaya, ¿y esto?, para mi que el método Math.Round no hacía esto, pero por lo que parece esto lo ha hecho de siempre así que la memoria nos ha gastado una mala pasada. Para que el redondeo sea el esperado debemos usar otra sobrecarga de dicho método, en la que podemos indicar que tipo de redondeo queremos usar, quedando nuestro código así
d1 = 123.465m;
d2 = Math.Round(d1, 2, MidpointRounding.AwayFromZero);
Print(d1, d2);
Obteniendo la siguiente salida
Valor: 123,465
Valor redondeado: 123,47
Así que, cuidado con los redondeos no sea que nos llevemos un susto en la facturación a final de año.

Happy coding!

No hay comentarios:

Publicar un comentario