Continuando minha tendência recente de jogar com grandes números , recentemente reduzi um erro que estava encontrando para o seguinte código:
DECLARE @big_number DECIMAL(38,0) = '1' + REPLICATE(0, 37);
PRINT @big_number + 1;
PRINT @big_number - 1;
PRINT @big_number * 1;
PRINT @big_number / 1;
A saída que recebo para este código é:
10000000000000000000000000000000000001
9999999999999999999999999999999999999
10000000000000000000000000000000000000
Msg 8115, Level 16, State 2, Line 6
Arithmetic overflow error converting expression to data type numeric.
O que?
Por que as 3 primeiras operações funcionariam, mas não a última? E como pode haver um erro de estouro aritmético se @big_number
obviamente pode armazenar a saída de @big_number / 1
?
Compreendendo a precisão e a escala no contexto das operações aritméticas
Vamos detalhar isso e dar uma olhada nos detalhes do operador aritmético de divisão . Isto é o que o MSDN tem a dizer sobre os tipos de resultado do operador de divisão :
Sabemos que
@big_number
é umDECIMAL
. Em que tipo de dados o SQL Server é convertido1
? Ele o lança em um arquivoINT
. Podemos confirmar isso com a ajuda deSQL_VARIANT_PROPERTY()
:Para começar, também podemos substituir o
1
no bloco de código original por um valor digitado explicitamente comoDECLARE @one INT = 1;
e confirmar que obtemos os mesmos resultados.Então nós temos um
DECIMAL
e umINT
. ComoDECIMAL
tem uma precedência de tipo de dados maior queINT
, sabemos que a saída de nossa divisão será convertida emDECIMAL
.Então, onde está o problema?
O problema é com a escala do
DECIMAL
na saída. Aqui está uma tabela de regras sobre como o SQL Server determina a precisão e a escala dos resultados obtidos em operações aritméticas:E aqui está o que temos para as variáveis nesta tabela:
De acordo com o comentário do asterisco na tabela acima, a precisão máxima que um
DECIMAL
pode ter é 38 . Portanto, nossa precisão de resultado é reduzida de 49 para 38 e "a escala correspondente é reduzida para evitar que a parte integral de um resultado seja truncada". Não está claro neste comentário como a escala é reduzida, mas sabemos disso:De acordo com a fórmula na tabela, a escala mínima possível que você pode ter depois de dividir dois
DECIMAL
s é 6.Assim, chegamos aos seguintes resultados:
Como isso explica o estouro aritmético
Agora a resposta é óbvia:
A saída da nossa divisão é convertida para
DECIMAL(38, 6)
, eDECIMAL(38, 6)
não pode conter 10 37 .Com isso, podemos construir outra divisão que seja bem-sucedida, garantindo que o resultado caiba em
DECIMAL(38, 6)
:O resultado é:
Observe os 6 zeros após a vírgula. Podemos confirmar que o tipo de dados do resultado é
DECIMAL(38, 6)
usandoSQL_VARIANT_PROPERTY()
como acima:Uma Solução Perigosa
Então, como contornar essa limitação?
Bem, isso certamente depende de para que você está fazendo esses cálculos. Uma solução para a qual você pode pular imediatamente é converter seus números
FLOAT
para os cálculos e, em seguida, convertê-los novamenteDECIMAL
quando terminar.Isso pode funcionar em algumas circunstâncias, mas você deve ter cuidado para entender quais são essas circunstâncias. Como todos sabemos, converter números de e para
FLOAT
é perigoso e pode fornecer resultados inesperados ou incorretos.No nosso caso, converter 10 37 de e para
FLOAT
obtém um resultado que está simplesmente errado :E aí está. Dividam com cuidado, meus filhos.