Articles

Los peligros de usar tipos de datos Flotantes o Reales

Hay una vieja broma sobre la aritmética de punto flotante:

«Si corto un pastel en tres, cada pieza es 0,33 del pastel. Si vuelvo a unir las tres piezas, me da 0,99 de la torta. ¿Dónde se ha ido el resto del pastel?»
– » Simple. «

La aritmética de coma flotante se trata de tolerar y administrar la aproximación para evitar errores de desbordamiento en los cálculos. En el mundo real, por lo general nos preocupamos por la precisión en los números y, en su lugar, sacrificamos espacio y recursos para evitar el desbordamiento.

Mientras que la ciencia funciona felizmente dentro de un margen de error, la precisión importa en la contabilidad empresarial. Cuando era un programador de cachorros, una vez escribí lo que pensé que era una forma perfectamente adecuada de calcular el beneficio de las transacciones de corredores de bolsa. En un millón de libras, era un centavo o dos a lo sumo. Estaba muy contento. Utilizó los cálculos inherentes al compilador PL / 1 que usamos en ese momento para desarrollar paquetes financieros. Les mostré la aplicación finamente elaborada, y se horrorizaron. Un centavo en un millón de libras parecía temerario para los comerciantes de la ciudad. No lo aceptarían. Me vi obligado a escribir un paquete decimal codificado en binario (BCD) en código ensamblador que era preciso.

El indicador SQL tiene una regla de análisis de código (BP023) que le alertará sobre el uso de tipos de datos FLOAT o REAL, debido a las importantes imprecisiones que pueden introducir en el tipo de cálculos que muchas organizaciones realizan rutinariamente en sus datos de SQL Server.

Tipos de datos de números aproximados

La aritmética de coma flotante se ideó en un momento en que era una prioridad ahorrar memoria al tiempo que daba una forma versátil de hacer cálculos que involucraban grandes números. Aunque todavía es útil para muchos tipos de cálculos científicos, particularmente aquellos que se ajustan al estándar de doble precisión IEEE 754 para aritmética de coma flotante, es, por necesidad, un compromiso. La pista está en el nombre de este tipo de datos y aritmética: ‘aproximado’. Los números de coma flotante no pueden representar con precisión todos los números reales: además, las operaciones de coma flotante no pueden representar con precisión todas las operaciones aritméticas. Sin embargo, el rango de magnitud del número que pueden contener es mucho mayor de lo que es posible en otros tipos numéricos, incluso si no siempre se mantiene con precisión.

Los problemas que surgen del uso de cálculos de coma flotante se deben al redondeo durante cálculos complejos, y se ven más a menudo si los datos están «mal condicionados», de modo que los pequeños cambios en la entrada se magnifican en la salida. Las inexactitudes son mucho menos evidentes con la mayor precisión de la representación de los números, pero siguen presentes, sin embargo. También hay algunas restricciones esotéricas en el uso de números que son válidos pero no se pueden representar en coma flotante, como tan(π/2), pero es probable que solo exciten a los matemáticos.

SQL Server de datos de punto flotante

El SQL Estándar tiene tres punto flotante, los tipos de datos aproximados, REALDOUBLEPRECISION y FLOAT(n). SQL Server cumple con esto, excepto que no tiene DOUBLEPRECISIONtipo de datos, utilizando FLOAT(53) en su lugar. Los tipos de datos FLOAT(24) y FLOAT(53) corresponden a Binary32 (Simple) y Binary64 (doble) en el estándar IEEE 754, y se almacenan en 4 y 8 bytes, y se mantienen 7 y 16 dígitos, en consecuencia. Son útiles cuando es importante que los cálculos produzcan el mismo resultado que una aplicación que utiliza.NET framework que también utiliza IEEE 754. El tipo de doble precisión también se requiere cuando los números exceden en su magnitud el máximo permitido por el tipo de datos DECIMAL (38 dígitos), aunque con pérdida de precisión. Por supuesto, los números aproximados no se pueden usar de manera confiable en ninguna prueba de igualdad, como una cláusula WHERE.

Cálculos usando el tipo de datos REAL (precisión única)

Probaré el tipo de datos REAL. El tipo de datos FLOAT(24), o menor, reacciona de la misma manera. Lo primero que debe recordar al experimentar con números de coma flotante en SQL Server es que los SSMS representan un número de coma flotante de una manera que disimula pequeñas diferencias. Por ejemplo:

1
SELECT Convert(REAL,0.100000001490116119384765625)

…da de 0,1

Para ver con más precisión qué valor se almacena en un número de punto flotante, usted tiene que utilizar el STR() función, la especificación de la precisión que usted realmente desea.

1
2
3
4
5

/*estoy siendo un poco injusto aquí, debido a que el número decimal 0.1 no es representable
en punto flotante; la representación binaria exacta tendría un «1100» secuencia
continuar sin cesar:*/
DECLARE @FirstApproximate REAL = 0.1
SELECCIONE Str(@FirstApproximate,20,16) –debe ser 0.100000001490116119384765625

Ya, esto es alarmante. Después de todo, estamos lidiando con datos con millones de filas, por lo que pequeños errores se acumularán a menos que, como ‘redondeo de banqueros’, se promedien. ¡Ese error ya está cerca del «centavo en un millón de libras» (1/ 240000000) que mencioné en la introducción!

Evitemos 0.1 y pongámoslo en un fenómeno de punto flotante. ¿Qué tal dividir 1 por 3? Seguramente esto no podría ser un problema.

1
2
3
4
5

INDICANDO @rentabilidades REALES = 1
INDICANDO @divisor REAL =3
SELECCIONE Str(@rentabilidades /@divisor,20,16) como el cociente
–produce 0.3333333432674408
– debe ser 0.3333333333333333

¡Vaya!. Se equivocó. Vale, es un pequeño error, pero recuerda mi historia sobre los banqueros. Una respuesta es correcta o incorrecta, no hay tonos de gris para los hombres con trajes grises. En la escuela de negocios, solo hay una garrapata y una cruz. No hay señal que signifique «lo suficientemente cerca».

Una prueba simple es dividir uno por números de uno a veinte. ¿Qué podría salir mal?

Podemos almacenar los resultados del cálculo numérico y de coma flotante, ambos convertidos en cadenas y luego comparamos las cadenas (tenga en cuenta que STR() puede colocar un espacio inicial que crea una complicación).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

DECLARE @divisor REAL, @dividend REAL = 1
DECLARE @FloatingPointCalculations TABLE (Divisor INT, QuotientFloatingPoint VARCHAR(20), QuotientNumeric VARCHAR(20));
DECLARE @ii INT = 1;
DECLARE @iiMax INT = 20;
WHILE (@ii <= @iiMax)
BEGIN
SELECT @divisor = @ii;
INSERT INTO @FloatingPointCalculations (Divisor, QuotientFloatingPoint,
QuotientNumeric)
SELECT @ii AS divisor, Str(@Dividend / @divisor, 20, 16) AS QuotientFloatingPoint,
Convert(VARCHAR(20), 1.0000000 / @ii) AS QuotientNumeric;
SELECT @ii += 1;
END;
SELECT The.Divisor, The.QuotientFloatingPoint, The.QuotientNumeric
FROM @FloatingPointCalculations AS The;

Now, what if we list the rows where the numbers don’t match?

1
2
3

SELECT The.Divisor, The.QuotientFloatingPoint, The.QuotientNumeric
FROM @FloatingPointCalculations The
WHERE Left(LTrim(The.QuotientFloatingPoint),16)<> Left(LTrim(The.QuotientNumeric),16)

Ugh! Solo donde el divisor era 1, 2, 4, 8 o 16 había un resultado correcto.

En caso de que espere que de alguna manera el flotador sea preciso y la versión numérica no, aquí está el cociente numérico calculado en Excel:

Cálculos utilizando FLOTADOR(25) o superior (doble precisión)

Si utiliza coma flotante de doble precisión, FLOAT(25) o superior, las pruebas se superan, debido a que la función STR() permite un máximo de dieciséis lugares a la derecha del punto decimal. Si hay más de 16, el resultado se trunca. El tipo de datos de doble precisión tiene dieciséis dígitos, mientras que el tipo de datos de precisión simple tiene siete. También habrá visto que el tipo de datos de precisión única obtiene los primeros siete dígitos correctamente. Del mismo modo, la doble precisión hace que los primeros dieciséis dígitos sean correctos. Podemos ampliar el número para ver la aproximación.

1
2

DECLARE @FirstApproximate FLOAT(53) = 10000000000000000.1
SELECCIONE Str(@FirstApproximate,40,16) COMO BigNumberWithaDecimal

Que la parte fraccionaria ha desaparecido, ¿no? Probablemente sea solo una pequeña diferencia, pero en algunos cálculos, puede causar problemas.

Conclusión

La aritmética de coma flotante es rápida y económica en el almacenamiento, pero proporciona un resultado aproximado. Es adecuado para aplicaciones científicas bien condicionadas, pero no para cálculos financieros, que exigen que un número sea «correcto» o «incorrecto». También tiene la desventaja adicional en una base de datos porque no puede probar de manera confiable y consistente dos números aproximados para la igualdad.

No es correcto decir que nunca debe usar números de coma flotante en tipos de datos SQL o en aritmética. Los tipos aproximados están en el estándar SQL para un propósito. Hoy en día, siempre me quedaría con el tipo de datos de punto flotante de doble precisión en SQL Server, donde hay un requisito adecuado. Son excelentes para fines tales como modelar sistemas meteorológicos o trazar trayectorias, pero no para los tipos de cálculos para los que es probable que la organización promedio use una base de datos.

Si detecta un uso erróneo de estos tipos, debe cambiar a un tipo DECIMALNUMERIC en su lugar. Si sabe que necesita aritmética de punto flotante y puede explicar por qué, probablemente sepa lo suficiente para evitar las trampas de punto flotante, como la que ocurrió en el famoso fallo del misil Patriot que provocó directamente 28 muertes.