Что такое число?
У меня есть простой вопрос для вашего «простого» вопроса: что именно вы подразумеваете под «числом»?
- Является ли
−0
число? - Как вы относитесь к
√−1
? - Является ли
⅝
или ⅔
числом? - Является ли
186,282.42±0.02
милями в секунду одним числом - или их два или три? - Является ли
6.02e23
числом? - Является ли
3.141_592_653_589
числом?Как насчет π
или ℯ
?И −2π⁻³ ͥ
? - Сколько чисел в
0.083̄
? - Сколько чисел в
128.0.0.1
? - Какое число содержит
⚄
?Как насчет ⚂⚃
? - Есть ли в
10,5 mm
один номер или два? - Является ли
∛8³
числом или их три? - Какое число представляет
ↀↀⅮⅭⅭⅬⅫ AUC
, 2762 или 2009? - Являются ли числа
४५६७
и ৭৮৯৮
? - А как насчет
0377
, 0xDEADBEEF
и 0b111101101
? - Является ли
Inf
числом?NaN
? - Является ли
④②
числом?Как насчет ⓰
? - Как вы относитесь к
㊅
? - Какое отношение
ℵ₀
и ℵ₁
имеет отношение к числам?Или ℝ
, ℚ
и ℂ
?
Предлагаемые шаблоны
Кроме того, вам знакомы эти шаблоны?Можете ли вы объяснить плюсы и минусы каждого из них?
/\D/
/^\d+$/
/^\p{Nd}+$/
/^\pN+$/
/^\p{Numeric_Value:10}$/
/^\P{Numeric_Value:NaN}+$/
/^-?\d+$/
/^[+-]?\d+$/
/^-?\d+\.?\d*$/
/^-?(?:\d+(?:\.\d*)?|\.\d+)$/
/^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/
/^((\d)(?(?=(\d))|$)(?(?{ord$3==1+ord$2})(?1)|$))$/
/^(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))$/
/^(?:(?:[0-9a-fA-F]{1,2}):(?:[0-9a-fA-F]{1,2}):(?:[0-9a-fA-F]{1,2}):(?:[0-9a-fA-F]{1,2}):(?:[0-9a-fA-F]{1,2}):(?:[0-9a-fA-F]{1,2}))$/
/^(?:(?:[+-]?)(?:[0123456789]+))$/
/(([+-]?)([0123456789]{1,3}(?:,?[0123456789]{3})*))/
/^(?:(?:[+-]?)(?:[0123456789]{1,3}(?:,?[0123456789]{3})*))$/
/^(?:(?i)(?:[+-]?)(?:(?=[0123456789]|[.])(?:[0123456789]*)(?:(?:[.])(?:[0123456789]{0,}))?)(?:(?:[E])(?:(?:[+-]?)(?:[0123456789]+))|))$/
/^(?:(?i)(?:[+-]?)(?:(?=[01]|[.])(?:[01]{1,3}(?:(?:[,])[01]{3})*)(?:(?:[.])(?:[01]{0,}))?)(?:(?:[E])(?:(?:[+-]?)(?:[01]+))|))$/
/^(?:(?i)(?:[+-]?)(?:(?=[0123456789ABCDEF]|[.])(?:[0123456789ABCDEF]{1,3}(?:(?:[,])[0123456789ABCDEF]{3})*)(?:(?:[.])(?:[0123456789ABCDEF]{0,}))?)(?:(?:[G])(?:(?:[+-]?)(?:[0123456789ABCDEF]+))|))$/
/((?i)([+-]?)((?=[0123456789]|[.])([0123456789]{1,3}(?:(?:[_,]?)[0123456789]{3})*)(?:([.])([0123456789]{0,}))?)(?:([E])(([+-]?)([0123456789]+))|))/
Я подозреваю, что некоторые из этих шаблонов выше могут служить вашим потребностям.Но я не могу сказать вам, какой из них или один - или, если нет, предоставит вам другой - потому что вы не сказали, что вы подразумеваете под «числом».
Как видите, существует огромное количество числовых возможностей: вполне вероятно, на самом деле их стоит.11
Ключ к предлагаемым шаблонам
Каждое приведенное ниже нумерованное объяснение описывает шаблон соответствующего нумерованного шаблона, указанного выше.
- Соответствует, если есть какие-либо не цифры в любом месте строки, включая пробелы, такие как разрывы строк.
- Соответствует только в том случае, если строка не содержит ничего, кроме цифр, с возможным исключением разрывов задней строки.Обратите внимание, что цифра определяется как имеющая свойство «Общая категория» Десятичное число, которое доступно как
\p{Nd}
, \p{Decimal_Number}
или \p{General_Category=Decimal_Number}
.Этот ход на самом деле является просто отражением тех кодовых точек, чья категория числового типа является десятичной, которая доступна как \p{Numeric_Type=Decimal}
. - Это то же самое, что 2 в большинстве языков регулярных выражений.Java здесь исключение, потому что она не отображает простые экранированные символы, такие как
\w
и \W
, \d
и \D
, \s
и \S
и \b
или \B
всоответствующее свойство Unicode.Это означает, что вы не должны использовать ни один из этих восьми односимвольных escape-символов для любых данных Unicode в Java, потому что они работают только на ASCII, даже если Java всегда использует символы Unicode внутри. - Это немного отличается от 3 в этомон не ограничен десятичными числами, но может быть любым числом вообще;то есть любой символ со свойством
\pN
, \p{Number}
или \p{General_Category=Number}
.К ним относятся \p{Nl}
или \p{Letter_Number}
для таких вещей, как римские цифры и \p{No}
или \p{Other_Number}
для подписанных и подписанных чисел, дробей и чисел в кружках - среди прочих, таких как счетные стержни. - Это соответствует толькоэти строки полностью состоят из чисел, десятичное значение которых равно 10, например,
Ⅹ
римская цифра десять и ⑩
, ⑽
, ⒑
, ⓾
, ❿
, ➉
и ➓
. - Только те строки, которые содержат символы без числового значения NaN;другими словами, все символы должны иметь числовые значения.
- Соответствует только десятичным числам,опционально с ведущим HYPHEN MINUS.
- То же, что 7, но теперь также работает, если знак плюс вместо минуса.
- Поиск десятичных чисел с дополнительным HYPHEN MINUS и дополнительным ПОЛНЫМ ОСТАНОВОМ плюс ноль или более десятичных чисел после.
- То же, что и 9, но не требует цифр перед точкой, если она есть после.
- Стандартное обозначение с плавающей точкой для C и многих других языков, допускающее научное обозначение.
- Находит числа, состоящие только из двух или более десятичных знаков любого сценария в порядке убывания, например, 987 или 54321. Это рекурсивное регулярное выражение включает в себя обратный вызов к коду Perl, который проверяет, имеет ли цифра прогнозного значения значение кодовой точки, которое является преемником текущая цифра; то есть его порядковая стоимость на единицу больше. Это можно сделать в PCRE, используя функцию C в качестве выноски.
- Это ищет действительный адрес IPv4 с четырьмя десятичными числами в допустимом диапазоне, например 128.0.0.1 или 255.255.255.240, но не 999.999.999.999.
- Здесь ищется действительный MAC-адрес, поэтому шесть пар двоеточия разделяют две шестнадцатеричные цифры ASCII.
- Это ищет целые числа в диапазоне ASCII с необязательным начальным знаком. Это нормальный шаблон для сопоставления целых чисел ASCII.
- Это похоже на 15, за исключением того, что для разделения групп по три требуется запятая.
- Это похоже на 15, за исключением того, что запятая для разделения групп теперь является необязательной.
- Это обычный шаблон для сопоставления чисел с плавающей точкой в стиле C в ASCII.
- Это похоже на 18, но для разделения групп по 3 и в базе-2 требуется запятая, а не в базе-10.
- Это похоже на 19, но в гексе. Обратите внимание, что дополнительный показатель теперь обозначается как G вместо E, так как E является действительной шестнадцатеричной цифрой.
- Это проверяет, что строка содержит число с плавающей точкой в стиле C, но с необязательным разделителем группировки каждые три цифры запятой или подчеркивания (LOW LINE) между ними. Он также сохраняет эту строку в группе захвата
\1
, делая ее доступной как $1
после успешного совпадения.
Источники и ремонтопригодность
Образцы с номерами 1,2,7–11 взяты из предыдущего воплощения списка Perl Часто задаваемые вопросы в вопросе «Как проверить ввод?». Этот раздел был заменен предложением использовать модуль Regexp :: Common , написанный Abigail и Damian Conway . Оригинальные шаблоны все еще можно найти в рецепте 2.1 Perl Cookbook , «Проверка, является ли строка допустимым числом», решения которой можно найти для головокружительного числа разнообразных языки, включая ada, общий lisp, groovy, guile, haskell, java, merd, ocaml, php, pike, python, rexx, ruby и tcl в проекте PLEAC .
Шаблон 12 может быть более разборчиво переписан
m{
^
(
( \d )
(?(?= ( \d ) ) | $ )
(?(?{ ord $3 == 1 + ord $2 }) (?1) | $ )
)
$
}x
Используется regex recursion , который встречается во многих движках шаблонов, включая Perl и все языки, полученные из PCRE. Но он также использует встроенный код для проверки своего второго условного шаблона; насколько мне известно, кодовые выноски доступны только в Perl и PCRE.
Шаблоны 13–21 были получены из вышеупомянутого модуля Regexp :: Common. Обратите внимание, что для краткости все они написаны без пробелов и комментариев, которые вы определенно захотите в производственном коде. Вот как это может выглядеть в режиме /x
:
$real_rx = qr{ ( # start $1 to hold entire pattern
( [+-]? ) # optional leading sign, captured into $2
( # start $3
(?= # look ahead for what next char *will* be
[0123456789] # EITHER: an ASCII digit
| [.] # OR ELSE: a dot
) # end look ahead
( # start $4
[0123456789]{1,3} # 1-3 ASCII digits to start the number
(?: # then optionally followed by
(?: [_,]? ) # an optional grouping separator of comma or underscore
[0123456789]{3} # followed by exactly three ASCII digits
) * # repeated any number of times
) # end $4
(?: # begin optional cluster
( [.] ) # required literal dot in $5
( [0123456789]{0,} ) # then optional ASCII digits in $6
) ? # end optional cluster
) # end $3
(?: # begin cluster group
( [E] ) # base-10 exponent into $7
( # exponent number into $8
( [+-] ? ) # optional sign for exponent into $9
( [0123456789] + ) # one or more ASCII digits into $10
) # end $8
| # or else nothing at all
) # end cluster group
) }xi; # end $1 and whole pattern, enabling /x and /i modes
С точки зрения разработки программного обеспечения, все еще есть несколько проблем со стилем, используемым в версии режима /x
, показанной выше. Во-первых, много повторений кода, где вы видите одно и то же [0123456789]
; что произойдет, если одна из этих последовательностей случайно пропустит цифру? Во-вторых, вы полагаетесь на позиционные параметры, которые вы должны учитывать. Это означает, что вы можете написать что-то вроде:
(
$real_number, # $1
$real_number_sign, # $2
$pre_exponent_part, # $3
$pre_decimal_point, # $4
$decimal_point, # $5
$post_decimal_point, # $6
$exponent_indicator, # $7
$exponent_number, # $8
$exponent_sign, # $9
$exponent_digits, # $10
) = ($string =~ /$real_rx/);
, которыйоткровенно отвратителен!Легко ошибиться в нумерации, трудно вспомнить, куда идут символические имена, и писать утомительно, особенно если вам не нужны все эти фрагменты.Переписать это, чтобы использовать именованные группы вместо просто пронумерованных.Опять же, я буду использовать синтаксис Perl для переменных, но содержимое шаблона должно работать везде, где поддерживаются именованные группы.
use 5.010; # Perl got named patterns in 5.10
$real_rx = qr{
(?<real_number>
# optional leading sign
(?<real_number_sign> [+-]? )
(?<pre_exponent_part>
(?= # look ahead for what next char *will* be
[0123456789] # EITHER: an ASCII digit
| [.] # OR ELSE: a dot
) # end look ahead
(?<pre_decimal_point>
[0123456789]{1,3} # 1-3 ASCII digits to start the number
(?: # then optionally followed by
(?: [_,]? ) # an optional grouping separator of comma or underscore
[0123456789]{3} # followed by exactly three ASCII digits
) * # repeated any number of times
) # end <pre_decimal_part>
(?: # begin optional anon cluster
(?<decimal_point> [.] ) # required literal dot
(?<post_decimal_point>
[0123456789]{0,} )
) ? # end optional anon cluster
) # end <pre_exponent_part>
# begin anon cluster group:
(?:
(?<exponent_indicator> [E] ) # base-10 exponent
(?<exponent_number> # exponent number
(?<exponent_sign> [+-] ? )
(?<exponent_digits> [0123456789] + )
) # end <exponent_number>
| # or else nothing at all
) # end anon cluster group
) # end <real_number>
}xi;
Теперь названы абстракции, что помогает.Вы можете вывести группы по именам, и вам нужны только те, которые вам небезразличны.Например:
if ($string =~ /$real_rx/) {
($pre_exponent, $exponent_number) =
@+{ qw< pre_exponent exponent_number > };
}
Есть еще одна вещь, чтобы сделать этот шаблон, чтобы сделать его еще более обслуживаемым.Проблема в том, что повторений еще слишком много, что означает, что его слишком легко изменить в одном месте, но не в другом.Если бы вы проводили анализ МакКейба, вы бы сказали, что его показатель сложности слишком высок.Большинство из нас просто скажут, что это слишком отступ.Это мешает следовать.Чтобы исправить все эти вещи, нам нужен «грамматический шаблон», в котором есть блок определения для создания именованных абстракций, который мы потом несколько позже рассматриваем как вызов подпрограммы в матче.
use 5.010; # Perl first got regex subs in v5.10
$real__rx = qr{
^ # anchor to front
(?&real_number) # call &real_number regex sub
$ # either at end or before final newline
##################################################
# the rest is definition only; think of ##
# each named buffer as declaring a subroutine ##
# by that name ##
##################################################
(?(DEFINE)
(?<real_number>
(?&mantissa)
(?&abscissa) ?
)
(?<abscissa>
(?&exponent_indicator)
(?&exponent)
)
(?<exponent>
(&?sign) ?
(?&a_digit) +
)
(?<mantissa>
# expecting either of these....
(?= (?&a_digit)
| (?&point)
)
(?&a_digit) {1,3}
(?: (?&digit_separator) ?
(?&a_digit) {3}
) *
(?: (?&point)
(?&a_digit) *
) ?
)
(?<point> [.] )
(?<sign> [+-] )
(?<digit_separator> [_,] )
(?<exponent_indicator> [Ee] )
(?<a_digit> [0-9] )
) # end DEFINE block
}x;
Посмотрите, как безумно лучше грамматический рисунок, чем исходный шаблон с шумом линии?Также намного проще получить правильный синтаксис: я набрал его без единой синтаксической ошибки регулярного выражения, которую нужно было исправить.(Хорошо, я набрал все остальные без синтаксических ошибок, но я делал это некоторое время.:)
Грамматические шаблоны выглядят гораздо больше как BNF, чем старые уродливые регулярные выражения, которыелюди пришли ненавидеть.Их гораздо проще читать, писать и поддерживать.Итак, давайте не будем больше мерзких узоров, хорошо?