Aritmética decimal en SQL Server: precisión y redondeo
Evita divisiones enteras, desbordamientos intermedios y diferencias de facturación mediante tipos numéricos adecuados y reglas explícitas de redondeo.
Declarar una columna decimal no convierte en exactos todos los cálculos que generan su valor. SQL Server evalúa las expresiones intermedias según los tipos de sus operandos. Puede perderse información antes de la asignación final y un agregado puede desbordarse aunque la columna de destino sea suficientemente grande. En facturación, estos errores resultan difíciles de detectar porque los importes suelen parecer razonables.
Primero define la magnitud del negocio. Importes, tipos de cambio, porcentajes y cantidades requieren distintos rangos. En decimal(p,s), p es el total de dígitos y s los situados después del separador decimal. decimal(12,2) deja diez dígitos para la parte entera. Elegir solo las posiciones decimales no completa el diseño.
Controlar las operaciones intermedias
Estas expresiones muestran por qué una conversión final puede llegar demasiado tarde.
SELECT 5 / 2 AS IntegerResult,
CAST(5 AS decimal(12,4)) / 2 AS DecimalResult,
CAST(5 / 2 AS decimal(12,4)) AS CastTooLate;
La división entre enteros devuelve 2. Convertir primero un operando conserva el resultado 2,5. Convertir después el resultado entero produce 2,0000; la mitad perdida no puede recuperarse.
Lo mismo ocurre al multiplicar. Dos int pueden desbordarse antes de que una conversión externa a bigint llegue a ejecutarse. Convierte un operando antes de la operación. El tipo de la columna donde guardarás el resultado no modifica la evaluación previa.
Las expresiones decimal también reciben precisión y escala calculadas. Para multiplicar, la regla inicial usa p1 + p2 + 1 como precisión y suma las escalas. SQL Server limita la precisión a 38 y aplica reglas adicionales que pueden reducir la escala o dejar un desbordamiento. La división puede ampliar mucho las posiciones decimales. Encadenar conversiones a decimal(38,...) no proporciona precisión ilimitada.
Calcula el mayor producto posible a partir de cantidad, precio y tipo de cambio, y establece el error final aceptable. Elige tipos intermedios capaces de representarlo. Si el controlador permite especificar precisión y escala de parámetros, hazlo explícitamente para que su definición no cambie según cada valor.
Acordar cuándo se redondea
Dos políticas de facturación válidas pueden producir totales distintos.
DECLARE @Lines table (Amount decimal(10,3));
INSERT @Lines VALUES (19.995), (19.995);
SELECT SUM(ROUND(Amount, 2)) AS RoundedPerLine,
ROUND(SUM(Amount), 2) AS RoundedInvoice
FROM @Lines;
Redondear cada línea de 19,995 a dos decimales y luego sumar produce 40,000. Sumar primero y redondear al final produce 39,990. El cero adicional mostrado corresponde a la escala conservada; la diferencia relevante es un céntimo.
La ubicación de ROUND no debería decidir accidentalmente la política. Acuerda si impuestos y descuentos se calculan por línea o por factura, en qué orden y dónde se asignan los restos. ROUND en SQL Server resuelve los casos exactamente intermedios alejándose de cero. Otro sistema puede aplicar una regla distinta y generar diferencias con entradas idénticas.
No sustituyas decimal por float simplemente para evitar un desbordamiento. float utiliza representación binaria aproximada y no encaja cuando el contrato exige coincidencia decimal exacta. Las mediciones científicas pueden justificar tipos aproximados. La elección depende del requisito, no de una prohibición universal.
Elegir el tipo antes de sumar
SUM sobre int devuelve int, aunque después asignes el resultado a bigint.
DECLARE @Counts table (Quantity int);
INSERT @Counts VALUES (2000000000), (2000000000);
SELECT SUM(CONVERT(bigint, Quantity)) AS TotalQuantity
FROM @Counts;
Aquí se amplía cada entrada antes de sumar y se obtiene 4.000.000.000. Convertir SUM(Quantity) posteriormente no evitaría el desbordamiento del agregado. SUM sobre decimal devuelve decimal(38,s), pero sigue teniendo un número finito de dígitos enteros.
Define además el significado de los valores ausentes. SUM ignora NULL y devuelve NULL cuando no hay entradas no nulas. Sustituirlo por cero puede ser correcto en un informe de cantidades, pero también ocultar datos financieros que faltan. Un precio desconocido no equivale a un artículo gratuito.
Incluye pruebas con divisiones fraccionarias, magnitudes máximas, reembolsos negativos, puntos exactos de redondeo, entradas vacías y comparación con la aplicación. Comprueba los resultados intermedios, además del texto final presentado al usuario. El formato puede disimular la pérdida de precisión, pero nunca recuperarla.
Referencias técnicas: Microsoft Learn: Precision, scale, and length · Microsoft Learn: SUM.