Qual é a lógica por trás de ISNUMERIC para certos caracteres especiais?

14

A ISNUMERICfunção tem algum comportamento inesperado. A documentação do MSDN diz:

ISNUMERICretorna 1 quando a expressão de entrada é avaliada para um tipo de dados numérico válido; caso contrário, ele retornará 0. Os tipos de dados numéricos válidos incluem o seguinte: int, bigint, smallint, tinyint, decimal, numeric, money, smallmoney, float, real .

E também tem uma nota de rodapé:

ISNUMERICretorna 1 para alguns caracteres que não são números, como mais (+), menos (-) e símbolos de moeda válidos, como o sinal de dólar ($). Para obter uma lista completa dos símbolos de moeda, consulte Dinheiro e dinheiro pequeno (Transact-SQL) .

Ok, então +, -e os símbolos de moeda listados devem ser considerados numéricos. Por enquanto, tudo bem.

Agora, a parte estranha. Primeiro, alguns dos símbolos de moeda do artigo vinculado não são numéricos, incluindo:

  • Sinal de moeda do euro, hex 20A0:
  • Sinal Naira, hexadecimal 20A6:
  • Sinal de rial, hexadecimal FDFC:

Isso é estranho, e eu não consigo descobrir por quê? Esta versão ou ambiente depende?

No entanto, as coisas ficam mais estranhas. Aqui estão alguns outros que não posso explicar:

  • /não é numérico, mas \é ( hein ?! )
  • REPLICATE(N'9', 308)é numérico, mas REPLICATE(N'9', 309)não é

A primeira e mais básica pergunta é: o que explica os casos acima? Mais importante ainda: qual é a lógica por trásISNUMERIC , para que eu pudesse explicar / prever todos os casos?

Aqui está uma boa maneira de reproduzir as coisas:

DECLARE @tbl TABLE(txt NVARCHAR(1000));

INSERT INTO @tbl (txt) 
VALUES (N''), (N' '), (N'€'), (N'$'), (N'$$'), 
       (NCHAR(8356)), (NCHAR(8352)), (NCHAR(8358)), (NCHAR(65020)), 
       (N'+'), (N'-'), (N'/'), (N'\'), (N'_'), (N'e'), (N'1e'), (N'e1'), (N'1e1'), 
       (N'1'), (N'-1'), (N'+1'), (N'1+1'), (N''), (N'🄂'), (N'¹'), (N''), (N'½'), 
       (N'🎅'), (REPLICATE(N'9', 307)), (REPLICATE(N'9', 308)), (REPLICATE(N'9', 309)), 
       (REPLICATE(N'9', 310));

SELECT  UNICODE(LEFT(txt, 1)) AS FirstCharAsInt,
        LEN(txt) AS TxtLength,
        txt AS Txt,
        ISNUMERIC(txt) AS [ISNUMERIC]
FROM    @tbl;

Quando executo isso na minha caixa local do Sql Server 2012, obtenho os seguintes resultados:

FirstCharAsInt   TxtLength   Txt        ISNUMERIC
---------------  ----------  ---------  ----------
NULL             0                      0
32               0                      0
8364             1           €          1
36               1           $          1
36               2           $$         0
8356             1           ₤          1
8352             1           ₠          0  --??
8358             1           ₦          0  --??
65020            1           ﷼‎          0  --??
43               1           +          1
45               1           -          1
47               1           /          0
92               1           \          1  --??
95               1           _          0
101              1           e          0
49               2           1e         0
101              2           e1         0
49               3           1e1        1
49               1           1          1
45               2           -1         1
43               2           +1         1
49               3           1+1        0
9352             1           ⒈         0
55356            2           🄂          0
185              1           ¹          0
9312             1           ①          0
189              1           ½          0
55356            2           🎅         0
57               307        /*...*/     1
57               308        /*...*/     1  --??
57               309        /*...*/     0  --??
57               310        /*...*/     0
Jeroen
fonte
Os únicos que me parecem incorretos são os relatórios falsos 0de cinco dos valores que realmente são válidos money. Os outros parecem precisos. SQL FIDDLE
Martin Smith
Embora NCHAR(0) - NCHAR(65535)eu veja 112 discrepâncias. Incluindo caracteres como os ₁,₂,₃,4,5,6,7,8,9que parecem numéricos, mas que não são convertidos com êxito em nada para mim. Fiddle
Martin Smith

Respostas:

13

Os comportamentos detalhados de ISNUMERICnão são documentados e provavelmente não são totalmente conhecidos por ninguém sem acesso ao código-fonte. Dito isto, pode ser que a interpretação dependa da categorização Unicode (numérica ou não). Da mesma forma, os casos estranhos que você menciona podem ser bugs preservados para compatibilidade com versões anteriores. Sim, eu sei que parece loucura, mas acontece.

Como você está usando o SQL Server 2012, não há necessidade de usar ISNUMERIC. Use TRY_CONVERTou o sinônimo TRY_CASTpara verificar se uma sequência é convertível para um determinado tipo. Onde eles fornecem funcionalidade adequada, é preferível TRY_PARSE, porque o último envolve um processamento mais caro por meio da integração CLR.

Paul White 9
fonte
2
E provavelmente também não é totalmente conhecido por muitas pessoas com acesso ao código-fonte. :-) Gostaria de poder marcar +1 novamente no segundo parágrafo. ISNUMERIC () é amplamente inútil porque sua intenção é determinar se algo pode ser convertido em pelo menos um tipo numérico; é obviamente muito mais importante saber que você pode converter para um único tipo numérico específico.
Aaron Bertrand
1
@AaronBertrand Parece haver um número razoavelmente significativo de casos em que ela nem atende a essa intenção também.
Martin Smith
9

A barra invertida ASCII (ponto de código 5C) compartilha o mesmo ponto de código que o sinal de iene (¥) na codificação Shift-JIS usada pela versão japonesa do Windows e o sinal de vitória (₩) no EUC-KR coreano. Portanto, é muito provável que seja apenas uma continuação do tema do sinal de moeda.

user47620
fonte
Ah, essa é uma teoria interessante. É moneyque ele lança também.
Martin Smith
@Jeroen - Está na Wikipedia FWIW
Martin Smith
3
@ Jeroen, receio que não. Alterne a página de código herdada da sua instalação do Windows para japonês e você encontrará caminhos como C:¥Program Files¥no explorer.exe
user47620