¿Cuál es la lógica detrás de ISNUMERIC para ciertos caracteres especiales?

14

La ISNUMERICfunción tiene un comportamiento inesperado. La documentación de MSDN dice:

ISNUMERICdevuelve 1 cuando la expresión de entrada se evalúa como un tipo de datos numéricos válido; de lo contrario, devuelve 0. Los tipos de datos numéricos válidos incluyen los siguientes: int, bigint, smallint, tinyint, decimal, numérico, money, smallmoney, float, real .

Y también tiene una nota al pie:

ISNUMERICdevuelve 1 para algunos caracteres que no son números, como más (+), menos (-) y símbolos de moneda válidos, como el signo de dólar ($). Para obtener una lista completa de los símbolos de moneda, vea money and smallmoney (Transact-SQL) .

De acuerdo, entonces +, -y los símbolos de moneda listados se considerarán numéricos. Hasta aquí todo bien.

Ahora para la parte extraña. En primer lugar, algunos de los símbolos de moneda del artículo vinculado no son numéricos, incluidos:

  • Signo de Euro-Moneda, hex 20A0:
  • Signo de Naira, hex 20A6:
  • Signo de Rial, hex FDFC:

Esto es extraño, y parece que no puedo entender por qué. ¿Es esta versión o entorno dependiente?

Sin embargo, las cosas se ponen más raras. Aquí hay algunos otros que no puedo explicar:

  • /no es numérico, pero \es (¿ eh? )
  • REPLICATE(N'9', 308)es numérico, pero REPLICATE(N'9', 309)no es

La primera y más básica pregunta es: ¿qué explica los casos anteriores? Más importante aún: ¿cuál es la lógica detrásISNUMERIC , para poder explicar / predecir todos los casos yo mismo?

Aquí hay una buena forma de reproducir cosas:

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;

Cuando ejecuto esto en mi cuadro local de SQL Server 2012 obtengo los siguientes 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
fuente
Los únicos que me parecen incorrectos son los informes falsos 0de cinco de los valores que realmente funcionan money. Los otros parecen precisos. SQL FIDDLE
Martin Smith
Aunque desde NCHAR(0) - NCHAR(65535)veo 112 discrepancias. Incluyendo personajes como los ₁,₂,₃,4,5,6,7,8,9que parecen numéricos pero no se lanzan con éxito a nada para mí. Fiddle
Martin Smith

Respuestas:

13

Los comportamientos detallados de ISNUMERICno están documentados y probablemente nadie los conozca sin acceso al código fuente. Dicho esto, puede ser que la interpretación dependa de la categorización Unicode (numérica o no). Del mismo modo, los casos extraños que menciona pueden ser errores que se conservan para la compatibilidad con versiones anteriores. Sí, sé que suena loco, pero sucede.

Como está utilizando SQL Server 2012, no hay necesidad de usarlo ISNUMERIC. Utilice TRY_CONVERTo el sinónimo en su TRY_CASTlugar para verificar si una cadena es convertible a un tipo dado. Cuando proporcionan una funcionalidad adecuada, estos son preferibles a TRY_PARSE, porque este último implica un procesamiento más costoso a través de la integración CLR.

Paul White 9
fuente
2
Y probablemente tampoco lo sepan muchas personas con acceso al código fuente. :-) Ojalá pudiera hacer +1 nuevamente para el segundo párrafo. ISNUMERIC () es en gran medida inútil porque su intención es determinar si algo se puede convertir al menos a un tipo numérico; Obviamente, es mucho más importante saber que puede convertir a un único tipo numérico específico.
Aaron Bertrand
1
@AaronBertrand Parece que hay un número razonablemente significativo de casos en los que ni siquiera cumple con esa intención.
Martin Smith
9

La barra invertida ASCII (punto de código 5C) comparte el mismo punto de código que el signo yen (¥) en la codificación Shift-JIS utilizada por la versión japonesa de Windows, y el signo ganado (₩) en EUC-KR coreano. Por lo tanto, es muy probable que solo sea una continuación del tema del signo de moneda.

usuario47620
fuente
Ah, esa es una teoría interesante. Es a lo moneyque se dirige también.
Martin Smith
@Jeroen - Está en Wikipedia FWIW
Martin Smith
3
@Jeroen, me temo que no. Cambie la página de códigos heredada de su instalación de Windows a japonés y obtendrá rutas como C:¥Program Files¥en explorer.exe
user47620