Какова логика ISNUMERIC для определенных специальных символов?

14

ISNUMERICФункция имеет некоторое неожиданное поведение. Документация MSDN гласит:

ISNUMERICвозвращает 1, когда входное выражение оценивается как допустимый числовой тип данных; в противном случае возвращается 0. Допустимые числовые типы данных включают следующие: int, bigint, smallint, tinyint, десятичное, числовое, money, smallmoney, float, real .

И это также имеет сноску:

ISNUMERICвозвращает 1 для некоторых символов, которые не являются числами, такими как плюс (+), минус (-), и допустимыми символами валюты, такими как знак доллара ($). Для полного списка символов валюты см. Money и smallmoney (Transact-SQL) .

Итак +, -и перечисленные символы валюты, как ожидается, будут считаться числовыми. Все идет нормально.

Теперь о странной части. Прежде всего, некоторые символы валюты из связанной статьи не являются числовыми, в том числе:

  • Знак евро-валюты, гексагон 20A0:
  • Знак наира, шестнадцатеричный 20А6:
  • Знак Rial, шестнадцатеричный FDFC:

Это странно, и я не могу понять, почему? Эта версия или среда зависят?

Тем не менее, все становится страннее. Вот несколько других, которые я не могу объяснить:

  • /не числовой, а \есть ( Ха ?! )
  • REPLICATE(N'9', 308)является числовым, но REPLICATE(N'9', 309)не

Первый и самый основной вопрос: что объясняет вышеупомянутые случаи? Что еще более важно: какая логика лежит в основеISNUMERIC , чтобы я мог объяснить / предсказать все случаи сам?

Вот хороший способ воспроизвести вещи:

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;

Когда я запускаю это на своем локальном сервере Sql Server 2012, я получаю следующие результаты:

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
Йерун
источник
Единственное, что мне кажется неверным, - это то, что он ложно сообщает 0о пяти значениях, которые на самом деле приводятся к money. Другие кажутся точными. SQL FIDDLE
Мартин Смит
Хотя из NCHAR(0) - NCHAR(65535)112 я вижу несоответствия. Включая такие символы, ₁,₂,₃,4,5,6,7,8,9которые выглядят числовыми, но не приводят меня ни к чему успешному. Скрипка
Мартин Смит

Ответы:

13

Подробное поведение ISNUMERICне документировано и, вероятно, не полностью известно никому без доступа к исходному коду. Тем не менее, возможно, что интерпретация зависит от классификации Unicode (числовой или нет). Точно так же странные случаи, о которых вы упоминаете, могут быть ошибками, которые сохраняются для обратной совместимости. Да, я знаю, это звучит безумно, но это случается.

Поскольку вы используете SQL Server 2012, нет необходимости использовать ISNUMERIC. Вместо этого используйте TRY_CONVERTили синоним, TRY_CASTчтобы проверить, может ли строка быть преобразована в данный тип. Там, где они обеспечивают адекватную функциональность, это предпочтительнее TRY_PARSE, поскольку последняя включает в себя более дорогую обработку посредством интеграции CLR.

Пол Уайт 9
источник
2
И, вероятно, не полностью известен многим людям с доступом к исходному коду. :-) Жаль, что я мог бы +1 снова за второй абзац. ISNUMERIC () в значительной степени бесполезен, потому что его намерение состоит в том, чтобы определить, можно ли что-то преобразовать хотя бы в один числовой тип; очевидно, что гораздо важнее знать, что вы можете преобразовать в один конкретный числовой тип.
Аарон Бертран
1
@AaronBertrand Похоже, что существует достаточно значительное количество случаев, когда оно даже не соответствует этому намерению.
Мартин Смит
9

Обратная косая черта ASCII (кодовая точка 5C) имеет ту же кодовую точку, что и знак иены (¥) в кодировке Shift-JIS, используемой в японской версии Windows, и знак выигрыша (₩) в корейском EUC-KR. Следовательно, это очень вероятно, просто продолжение темы знак валюты.

user47620
источник
Ах, это интересная теория. Это moneyто, что он бросает также.
Мартин Смит
@Jeroen - это в Википедии FWIW
Мартин Смит
3
@Jeroen Боюсь, что нет. Переключите устаревшую кодовую страницу вашей установки Windows на японский, и вы получите пути, например, C:¥Program Files¥в explorer.exe
user47620