El misterio de Round

Hoy gracias a una incidencia de un cliente nos hemos vuelto a acordar del método Round, un método a priori muy sencillo pero cuya complejidad es mayor de la esperada. Vamos a hablar de los matices entre el Round de SQL Server y el de .net, resumo primero lo que ya sabíamos y después comento lo que hemos descubierto hoy. Os anticipo que los matices (lo bonito… el infierno) está en el punto medio.

SQL Server

La implementación de Round de SQL Server aproxima al número más cercano con el número de decimales solicitado. Vamos a hacer algunos ejemplos que podéis probar directamente en un SQL Server Management Studio:

SELECT Round(3.124, 2)
-> 3.12
SELECT Round(3.126, 2)
-> 3.13

Lo que uno se espera. ¿Pero qué pasa si no hay un número más cercano sino dos? Es decir, ¿si no encontramos en el centro? ¿Qué pasa al redondear 3.125? Pues bien, SQL Server lo redondea “hacia arriba”:

SELECT Round(3.125, 2)
-> 3.13

Entrecomillo hacia arriba ya que no es siempre así. ¿Y si el número es negativo?

SELECT Round(-3.125, 2)
-> -3.13

Ahora no es “hacia arriba”, por lo que el nombre técnico de esta regla de redondeo es Away from zero: alejarse del cero.

.net

Vamos con .net. En la clase Math podemos encontrar el método Round, de uso muy similar a SQL Server, y uno piensa que de comportamiento también… ¡pues no! Y pongo 1 ejemplo:

Math.Round(3.125, 2)
-> -3.12

(Esto lo podéis probar con LinqPad, nos sirve para ejecutar cualquier código de C# o VB, incluso F#, en un modo consola muy cómodo, sobre todo para aprender).
Bueno, ¿entonces qué? ¿.net redondea hacia el cero? Probamos otro número:

Math.Round(3.135, 2)
-> -3.14

¡Hala! Ya la hemos liado. Esto hace lo que quiere… Esto es lo que diría el programador incauto (tal cual lo dijimos nosotros la primera vez). Pero tras documentarnos, descubrimos la maravilla: el método tiene un parámetro opcional mode de tipo MidpointRounding, que determina qué hacer cuando estamos a igual distancia de las dos aproximaciones. Hay dos posibles estrategias:

  • ToEven: al número par (si os fijáis es el comportamiento descrito anteriormente).
  • AwayFromZero: alejándose de 0 (como hacía SQL Server más arriba).

Y obviamente, la que aplica por defecto .net es la primera, mientras que SQL Server es la segunda. Además, queda establecido que en .net puede cambiarse el comportamiento a voluntad, mientras que en SQL no.
Un detalle más: creo que al trabajar con dinero, lo que establecen las autoridades económicas españolas (por directiva europea, supongo, lo mío no es la Economía ni las Leyes, bastante tengo con la Programación) es una estrategia AwayFromZero, que ¡ojo! no es la estrategia por defecto en .net.

Nuestro descubrimiento de hoy

Pues bien, hoy nos hemos preguntado: ¿seguro que SQL Server no soporta el modo ToEven? Vamos a intentar colársela. Vamos a hacer una consulta Linq que realice un Round, y a ver cómo lo traduce EntityFramework a SQL. Bueno, finalmente hemos probado con Linq to SQL porque LinqPad nos lo pone más fácil. La consulta es:

Articulos.Select(a => Math.Round(a.PrecioVenta,2))

Una cosita simple: redondear los precios de los artículos y devolverlos en una enumeración absurda (muchas cantidades seguidas sin clave ni orden ni concierto). La intención, al no indicar mode, es usar ToEven, valor por defecto de Math.Round, y ver qué devolvía la base de datos y con qué consulta SQL.
Pues bien, no es posible. Se produce el error siguiente: NotSupportedException: Para la conversión a SQL, el método Math.Round necesita un parámetro MidpointRounding. Utilice ‘AwayFromZero’ para especificar la función SQL ROUND. Queda claro. Hay que especificar explícitamente AwayFromZero para usar Math.Round en una consulta Linq to SQL o EF. Si queremos el otro sistema de redondeo, hay que hacerlo en memoria, sobre los resultados de la consulta.

Resumiendo

Hay que tener cuidado con los diferentes modos de aproximación de Round en el punto medio.
Hay que usar el que se adapte a nuestro problema (o el que nos exija la legislación vigente).
Y al trabajar con SQL Server, sólo tenemos disponible 1 de ellos, AwayFromZero, y además hay que indicarlo explícitamente.

Anuncios
Esta entrada fue publicada en Linq. Guarda el enlace permanente.

2 respuestas a El misterio de Round

  1. Marcos dijo:

    Menudas chapuzas hacen esta gente de microsoft, porque esto es una chapuza muy grande, lo que nos indica queridos amigos es que no saben trabajar en equipo, dejan pasar detalles muy importantes y lo peor es que con todo es que se piensan que son lo mejores del mundo.

    Con lo que nos dan (leguajes de programacion, BBDD, etc, etc) se supone que tenemos que hacer buenos programas?? IMPOSIBLE!!, sin nos dan lenguages inutiles solo podremos hacer cosas inutiles.

    Marcos

  2. Dial Ruppert dijo:

    Hoy me tocó a mí! Pagos a cuenta de $ 0,01 porque descuentos calculados en el cliente (.NET 4) dan diferente al recalcularse en el server (SQLServer 2008).

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s