Почему Char.IsDigit возвращает true для символов, которые не могут быть проанализированы для int?

Я часто использую Char.IsDigit, чтобы проверить, является ли char цифрой, которая особенно удобна в запросах LINQ для предварительной проверки int.Parse, как здесь: "123".All(Char.IsDigit).

Но есть символы, которые являются цифрами, но которые не могут быть проанализированы на int как ۵.

// true
bool isDigit = Char.IsDigit('۵'); 

var cultures = CultureInfo.GetCultures(CultureTypes.SpecificCultures);
int num;
// false
bool isIntForAnyCulture = cultures
    .Any(c => int.TryParse('۵'.ToString(), NumberStyles.Any, c, out num)); 

Почему? Неверный ли мой int.Parse -precheck через Char.IsDigit?

Есть 310 символов, которые являются цифрами:

List<char> digitList = Enumerable.Range(0, UInt16.MaxValue)
   .Select(i => Convert.ToChar(i))
   .Where(c => Char.IsDigit(c))
   .ToList(); 

Здесь реализация Char.IsDigit в .NET 4 (ILSpy):

public static bool IsDigit(char c)
{
    if (char.IsLatin1(c))
    {
        return c >= '0' && c <= '9';
    }
    return CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.DecimalDigitNumber;
}

Итак, почему существуют символы, которые принадлежат DecimalDigitNumber -category ( "Символ десятичной цифры, то есть символ в диапазоне 0 до 9..." ), которые не могут быть проанализированы на int в любой культуре?

Ответ 1

Это потому, что он проверяет все цифры в категории "Число, десятичная цифра" в Юникоде, как указано здесь:

http://www.fileformat.info/info/unicode/category/Nd/list.htm

Это не означает, что он является допустимым числовым символом в текущей локали. Фактически, используя int.Parse(), вы можете ТОЛЬКО анализировать обычные английские цифры независимо от настроек локали.

Например, это не работает:

int test = int.Parse("٣", CultureInfo.GetCultureInfo("ar"));

Даже если ٣ является действительным символом арабской цифры, а "ar" является идентификатором арабского языка.

В статье Microsoft "Как: Разделить Unicode Digits" говорится, что:

Единственные Unicode-цифры, которые .NET Framework анализирует как десятичные знаки, это цифры ASCII от 0 до 9, заданные значениями кода U + 0030 через U + 0039..NET Framework анализирует все остальные символы Unicode в качестве символов.

Однако обратите внимание, что вы можете использовать char.GetNumericValue(), чтобы преобразовать числовой символ Unicode в его числовой эквивалент как двойной.

Причина, по которой возвращаемое значение является двойным, а не int, происходит из-за таких вещей:

Console.WriteLine(char.GetNumericValue('¼')); // Prints 0.25

Вы можете использовать что-то вроде этого, чтобы преобразовать все числовые символы в строке в их эквивалент ASCII:

public string ConvertNumericChars(string input)
{
    StringBuilder output = new StringBuilder();

    foreach (char ch in input)
    {
        if (char.IsDigit(ch))
        {
            double value = char.GetNumericValue(ch);

            if ((value >= 0) && (value <= 9) && (value == (int)value))
            {
                output.Append((char)('0'+(int)value));
                continue;
            }
        }

        output.Append(ch);
    }

    return output.ToString();
}

Ответ 2

Десятичные цифры от 0 до 9, но у них много представлений в Unicode. Из Wikipedia:

Десятичные цифры повторяются в 23 отдельных блоках

MSDN указывает, что .NET анализирует только латинские цифры:

Однако единственными числовыми цифрами, распознанными методами анализа, являются базовые латинские цифры 0-9 с кодовыми точками от U + 0030 до U + 0039