2010-03-03 17 views
7

Estamos atrapados con una base de datos que (desafortunadamente) usa flotantes en lugar de valores decimales. Esto hace que el redondeo sea un poco difícil. Considere el ejemplo siguiente (SQL Server T-SQL):"Medio redondeado" en valores de punto flotante

SELECT ROUND(6.925e0, 2) --> returns 6.92 

ROUND hace round half up, pero desde floating point numbers cannot accurately represent decimal numbers, se muestra el resultado "malo" (desde el punto de vista del usuario final). Entiendo por qué esto sucede.

ya me ocurrió con dos soluciones posibles (tanto devuelve un real, que es, por desgracia, también es un requisito):

  1. convertir a un tipo de datos decimal antes del redondeo: SELECT CONVERT(float, ROUND(CONVERT(decimal(29,14), 6.925e0), 2))
  2. Multiplicar hasta el tercer dígito está en el lado izquierdo de la coma decimal (es decir, representados con precisión), y luego hacer el redondeo: SELECT ROUND(6.925e0 * 1000, -1)/1000

Cuál debería elegir? ¿Hay alguna solución mejor? (Por desgracia, no podemos cambiar los tipos de campo en la base de datos debido a algunas aplicaciones heredadas acceso a la misma base de datos.)

¿Existe una solución bien establecida las mejores prácticas para este problema (común?)?

(Obviamente, la técnica común "redondeo dos veces" no ayudará aquí desde 6.925 está ya redondeado a tres decimales - por lo que esto es posible en un flotador.)

+1

Lo etiqueté tanto 'sql-server' como' language-agnostic', ya que los ejemplos están en T-SQL pero la pregunta sobre un algoritmo de mejores prácticas es genérica. – Heinzi

+0

No existe un algoritmo o una función que pueda lograr lo que desea porque la mayoría de los flotantes que son múltiplos de una fracción de base 10 no son exactamente representables. Por ejemplo, 6.925 se representa como un valor ligeramente superior o inferior, pero nunca exacto. ¿De qué manera quieres que se redondee? – hirschhornsalz

Respuesta

6

Su primera solución parece más seguro , y también parece un ajuste conceptual más cercano al problema: conviértalo cuanto antes de float a decimal, haga todos los cálculos relevantes dentro del tipo decimal, y luego haga una conversión de último minuto para flotar antes de escribir en el DB.

Editar: Es probable que aún necesite hacer una ronda extra (por ejemplo, hasta 3 decimales, o lo que sea apropiado para su aplicación) inmediatamente después de recuperar el valor flotante y convertirlo a decimal, para asegurarse de que termina con el valor decimal que realmente se pretendía 6.925e0 convertida a decimal sería nuevamente probable (suponiendo que el formato decimal tenga> 16 dígitos de precisión) para dar algo que esté muy cerca, pero no exactamente igual a 6.925; una ronda extra se encargaría de esto.

La segunda solución no parece confiable para mí: ¿qué sucede si el valor almacenado para 6.925e0 resulta ser, debido a los problemas de coma flotante binarios habituales, una pequeña cantidad demasiado pequeña? Luego, después de la multiplicación por 1000, el resultado puede seguir siendo un toque por debajo de 6925, por lo que el redondeo redondea hacia abajo en lugar de hacia arriba. Si sabe que su valor siempre tiene como máximo 3 dígitos después del punto, puede solucionarlo haciendo una ronda extra después de multiplicando por 1000, algo así como ROUND(ROUND(x * 1000, 0), -1).

(Negación: aunque no tengo mucha experiencia en tratar con flotador y cuestiones decimales en otros contextos, sé casi nada acerca de SQL.)

0

Usar un formato de precisión arbitraria como decimal. De esa manera, puede dejar que el lenguaje lo haga correctamente (o incorrecto según sea el caso).

+0

Obviamente, sí. Lamentablemente, como se indica en la pregunta, esta no es una opción. – Heinzi

+0

¿De verdad? Pensé que lo habías implementado en el punto 1 anterior. –

+0

Ah, vale, entonces malentendí tu respuesta; Pensé que te estabas refiriendo a cambiar el tipo de datos en la base de datos. Gracias por la aclaración. – Heinzi

0

I lograron redondear la columna de la float correctamente utilizando el comando siguiente:

CONVERT SELECT (float, ROUND (ROUND (CONVERTIR (decimal (38,14), float_column_name), 3), 2))

2

Pregunta anterior, pero estoy sorprendido de que la práctica habitual no se menciona aquí, así que simplemente la agrego. Normalmente, debe agregar una pequeña cantidad que sepa que es mucho más pequeña que la precisión de los números con los que está trabajando, p. como este:

SELECT ROUND(6.925e0 + 1e-7, 2) 

Por supuesto, la cantidad agregada debe ser mayor que la precisión del tipo de punto flotante que se utiliza.

+0

Guau, eso suena como una solución realmente simple y efectiva. ¡Gracias! ¿Esta idea está documentada en alguna parte? (Usted mencionó que es una "práctica normal" ...) – Heinzi

+0

Bueno, no sé sobre la documentación, pero la he estado utilizando durante 25 años. La limitación, por supuesto, es que necesita saber el rango de números con los que está trabajando. Pero en la mayoría de las situaciones prácticas, y ciertamente en el redondeo, ese es el caso. – fishinear

Cuestiones relacionadas