AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / dba / Perguntas / 18997
Accepted
Nick Chammas
Nick Chammas
Asked: 2012-06-09 11:47:32 +0800 CST2012-06-09 11:47:32 +0800 CST 2012-06-09 11:47:32 +0800 CST

Por que 10 ^ 37 / 1 gera um erro de estouro aritmético?

  • 772

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_numberobviamente pode armazenar a saída de @big_number / 1?

sql-server sql-server-2008
  • 1 1 respostas
  • 1392 Views

1 respostas

  • Voted
  1. Best Answer
    Nick Chammas
    2012-06-09T11:47:32+08:002012-06-09T11:47:32+08:00

    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 :

    Tipos de resultados

    Retorna o tipo de dados do argumento com maior precedência. Para obter mais informações, consulte Precedência de tipo de dados (Transact-SQL) .

    Se um dividendo inteiro for dividido por um divisor inteiro, o resultado será um inteiro com qualquer parte fracionária do resultado truncada.

    Sabemos que @big_numberé um DECIMAL. Em que tipo de dados o SQL Server é convertido 1? Ele o lança em um arquivo INT. Podemos confirmar isso com a ajuda de SQL_VARIANT_PROPERTY():

    SELECT
          SQL_VARIANT_PROPERTY(1, 'BaseType')   AS [BaseType]  -- int
        , SQL_VARIANT_PROPERTY(1, 'Precision')  AS [Precision] -- 10
        , SQL_VARIANT_PROPERTY(1, 'Scale')      AS [Scale]     -- 0
    ;
    

    Para começar, também podemos substituir o 1no bloco de código original por um valor digitado explicitamente como DECLARE @one INT = 1;e confirmar que obtemos os mesmos resultados.

    Então nós temos um DECIMALe um INT. Como DECIMALtem uma precedência de tipo de dados maior que INT, sabemos que a saída de nossa divisão será convertida em DECIMAL.

    Então, onde está o problema?

    O problema é com a escala do DECIMALna 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:

    Operation                              Result precision                       Result scale *
    -------------------------------------------------------------------------------------------------
    e1 + e2                                max(s1, s2) + max(p1-s1, p2-s2) + 1    max(s1, s2)
    e1 - e2                                max(s1, s2) + max(p1-s1, p2-s2) + 1    max(s1, s2)
    e1 * e2                                p1 + p2 + 1                            s1 + s2
    e1 / e2                                p1 - s1 + s2 + max(6, s1 + p2 + 1)     max(6, s1 + p2 + 1)
    e1 { UNION | EXCEPT | INTERSECT } e2   max(s1, s2) + max(p1-s1, p2-s2)        max(s1, s2)
    e1 % e2                                min(p1-s1, p2 -s2) + max( s1,s2 )      max(s1, s2)
    
    * The result precision and scale have an absolute maximum of 38. When a result 
      precision is greater than 38, the corresponding scale is reduced to prevent the 
      integral part of a result from being truncated.
    

    E aqui está o que temos para as variáveis ​​nesta tabela:

    e1: @big_number, a DECIMAL(38, 0)
    -> p1: 38
    -> s1: 0
    
    e2: 1, an INT
    -> p2: 10
    -> s2: 0
    
    e1 / e2
    -> Result precision: p1 - s1 + s2 + max(6, s1 + p2 + 1) = 38 + max(6, 11) = 49
    -> Result scale:                    max(6, s1 + p2 + 1) =      max(6, 11) = 11
    

    De acordo com o comentário do asterisco na tabela acima, a precisão máxima que um DECIMALpode 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 DECIMALs é 6.

    Assim, chegamos aos seguintes resultados:

    e1 / e2
    -> Result precision: 49 -> reduced to 38
    -> Result scale:     11 -> reduced to 6  
    
    Note that 6 is the minimum possible scale it can be reduced to. 
    It may be between 6 and 11 inclusive.
    

    Como isso explica o estouro aritmético

    Agora a resposta é óbvia:

    A saída da nossa divisão é convertida para DECIMAL(38, 6), e DECIMAL(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):

    DECLARE @big_number    DECIMAL(38,0) = '1' + REPLICATE(0, 37);
    DECLARE @one_million   INT           = '1' + REPLICATE(0, 6);
    
    PRINT @big_number / @one_million;
    

    O resultado é:

    10000000000000000000000000000000.000000
    

    Observe os 6 zeros após a vírgula. Podemos confirmar que o tipo de dados do resultado é DECIMAL(38, 6)usando SQL_VARIANT_PROPERTY()como acima:

    DECLARE @big_number   DECIMAL(38,0) = '1' + REPLICATE(0, 37);
    DECLARE @one_million  INT           = '1' + REPLICATE(0, 6);
    
    SELECT
          SQL_VARIANT_PROPERTY(@big_number / @one_million, 'BaseType')  AS [BaseType]  -- decimal
        , SQL_VARIANT_PROPERTY(@big_number / @one_million, 'Precision') AS [Precision] -- 38
        , SQL_VARIANT_PROPERTY(@big_number / @one_million, 'Scale')     AS [Scale]     -- 6
    ;
    

    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 FLOATpara os cálculos e, em seguida, convertê-los novamente DECIMALquando 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 FLOATobtém um resultado que está simplesmente errado :

    DECLARE @big_number     DECIMAL(38,0)  = '1' + REPLICATE(0, 37);
    DECLARE @big_number_f   FLOAT          = CAST(@big_number AS FLOAT);
    
    SELECT
          @big_number                           AS big_number      -- 10^37
        , @big_number_f                         AS big_number_f    -- 10^37
        , CAST(@big_number_f AS DECIMAL(38, 0)) AS big_number_f_d  -- 9999999999999999.5 * 10^21
    ;
    

    E aí está. Dividam com cuidado, meus filhos.

    • 18

relate perguntas

  • Quais são as principais causas de deadlocks e podem ser evitadas?

  • Quanto "Padding" coloco em meus índices?

  • Existe um processo do tipo "práticas recomendadas" para os desenvolvedores seguirem para alterações no banco de dados?

  • Como determinar se um Índice é necessário ou necessário

  • Downgrade do SQL Server 2008 para 2005

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Como ver a lista de bancos de dados no Oracle?

    • 8 respostas
  • Marko Smith

    Quão grande deve ser o mysql innodb_buffer_pool_size?

    • 4 respostas
  • Marko Smith

    Listar todas as colunas de uma tabela especificada

    • 5 respostas
  • Marko Smith

    restaurar a tabela do arquivo .frm e .ibd?

    • 10 respostas
  • Marko Smith

    Como usar o sqlplus para se conectar a um banco de dados Oracle localizado em outro host sem modificar meu próprio tnsnames.ora

    • 4 respostas
  • Marko Smith

    Como você mysqldump tabela (s) específica (s)?

    • 4 respostas
  • Marko Smith

    Como selecionar a primeira linha de cada grupo?

    • 6 respostas
  • Marko Smith

    Listar os privilégios do banco de dados usando o psql

    • 10 respostas
  • Marko Smith

    Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Como faço para listar todos os bancos de dados e tabelas usando o psql?

    • 7 respostas
  • Martin Hope
    Mike Walsh Por que o log de transações continua crescendo ou fica sem espaço? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland Listar todas as colunas de uma tabela especificada 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney O MySQL pode realizar consultas razoavelmente em bilhões de linhas? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx Como posso monitorar o andamento de uma importação de um arquivo .sql grande? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison Como você mysqldump tabela (s) específica (s)? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    pedrosanta Listar os privilégios do banco de dados usando o psql 2011-08-04 11:01:21 +0800 CST
  • Martin Hope
    Jonas Como posso cronometrar consultas SQL usando psql? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas Como faço para listar todos os bancos de dados e tabelas usando o psql? 2011-02-18 00:45:49 +0800 CST
  • Martin Hope
    bernd_k Quando devo usar uma restrição exclusiva em vez de um índice exclusivo? 2011-01-05 02:32:27 +0800 CST

Hot tag

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve