Pregunta

Al depurar algún código SQL relacionado con las finanzas se encontró un problema extraño con la precisión matemática numérica (24,8).

Al ejecutar la siguiente consulta en su MSSQL, el resultado de la expresión A + B * C será 0.123457

Seleccione A, B, C, A + B * C de (Seleccione Cast (0.12345678 como Numérico (24,8)) como A, Cast (0 como Numérico (24,8)) como B, Cast (500 como Numérico (24 , 8)) como c) t

Entonces hemos perdido 2 símbolos significativos.Al intentar solucionar esto de diferentes maneras, obtuve que la conversión del resultado de la multiplicación intermedia (¡que es cero!) a numérico (24,8) funcionaría bien.

Y finalmente tengo una solución.Pero todavía tengo una pregunta: ¿por qué MSSQL se comporta de esta manera y qué tipo de conversiones ocurrieron realmente en mi muestra?

¿Fue útil?

Solución

Así como la suma del tipo flotante es inexacta, la multiplicación de los tipos decimales puede ser inexacta (o causar inexactitud) si se excede la precisión.Ver Conversión de tipo de datos y decimales y numéricos.

Desde que multiplicaste NUMERIC(24,8) y NUMERIC(24,8), y SQL Server solo verificará el tipo, no el contenido, probablemente intentará guardar los 16 dígitos no decimales potenciales (24 - 8) cuando no puede guardar los 48 dígitos de precisión (el máximo es 38).Combina dos de ellos y obtienes 32 dígitos no decimales, lo que te deja con sólo 6 dígitos decimales (38 - 32).

Así la consulta original

SELECT A, B, C, A + B * C
FROM ( SELECT CAST(0.12345678 AS NUMERIC(24,8)) AS A,
  CAST(0 AS NUMERIC(24,8)) AS B,
  CAST(500 AS NUMERIC(24,8)) AS C ) T

reduce a

SELECT A, B, C, A + D
FROM ( SELECT CAST(0.12345678 AS NUMERIC(24,8)) AS A,
  CAST(0 AS NUMERIC(24,8)) AS B,
  CAST(500 AS NUMERIC(24,8)) AS C,
  CAST(0 AS NUMERIC(38,6)) AS D ) T

De nuevo, entre NUMERIC(24,8) y NUMERIC(38,6), SQL Server intentará guardar los posibles 32 dígitos no decimales, por lo que A + D reduce a

SELECT CAST(0.12345678 AS NUMERIC(38,6))

que te da 0.123457 después del redondeo.

Otros consejos

Siguiendo la lógica señalada por eed3si9n y lo que dijiste en tu pregunta parece que el mejor enfoque al realizar operaciones matemáticas es extraerlas en una función y, además, especificar la precisión después de cada operación,

En este caso, la función podría verse así:

create function dbo.myMath(@a as numeric(24,8), @b as numeric(24,8), @c as numeric(24,8))
returns  numeric(24,8)
as
begin 
    declare @d as numeric(24,8)
    set @d = @b* @c
    return @a + @d
end

A pesar de lo que dice Precisión, escala y longitud (Transact-SQL).Creo que también está aplicando una 'escala' mínima (número de decimales) de 6 al tipo NUMÉRICO resultante para la multiplicación, al igual que para la división, etc.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top