разбирать поплавок в java с трейлинг-барахлом - PullRequest
3 голосов
/ 27 февраля 2020

Справочная информация: я пытаюсь постепенно разбирать выражения, такие как "cos (1.2)". Теперь к фактическому вопросу ( примечание: что фактический вопрос в основном в следующем параграфе; остальное - разговоры о решениях, которые, кажется, почти работают ):

Предположим, у меня есть строка в Java, который может начинаться с числа с плавающей запятой, а затем после него есть еще несколько «вещей». Например, у меня может быть 52hi (который начинается с «52» и заканчивается «hi») или -1.2e1e9 (который начинается с «-1.2e1», также известный как «отрицательный двенадцать» и заканчивается «e9» ). Я хочу разобрать это число в двойное число.

Заманчиво использовать Double.parseDouble, но этот метод ожидает, что строка в целом является допустимым числом, и выдает исключение, если нет. Очевидная вещь, которую нужно сделать, это написать регулярное выражение, чтобы отделить число от других вещей, а затем использовать parseDouble.

Если бы я разбирал целые числа, это было бы не так уж плохо, что-то вроде -?[0-9]+ , (Даже тогда легко забыть крайний случай, и теперь ваши пользователи не могут ввести +9 для симметрии с -9. Поэтому предыдущее регулярное выражение должно было быть [-+]?[0-9]+.) Но для float это сложно; может быть что-то вроде этого (игнорируйте тот факт, что "." не воспринимается буквально по умолчанию в большинстве диалектов регулярных выражений):

[-+]?[0-9]*.?[0-9]*(e[-+]?[0-9]+)?.

За исключением того, что мы только что сказали, что пустая строка является действительный номер. И так ".e2". Так что, возможно, что-то немного сложнее. Или, может быть, у меня может быть «неаккуратное» регулярное выражение, подобное приведенному выше, которое допускает некоторые не числа, если это не запрещает какие-либо действительные числа. Но в какой-то момент я начинаю думать про себя: «Разве это не должно быть работой parseDouble?». Он выполняет большую часть работы, необходимой для выяснения, где в строке заканчивается число и начинается другой материал, потому что в противном случае он не смог бы выдать исключение. Зачем мне это делать?

Поэтому я начал искать, есть ли в стандартной библиотеке Java что-нибудь еще, что могло бы помочь. Мой обычный инструмент выбора - java .util.Scanner, у которого есть хороший метод nextDouble (). Но Scanner работает с «токенами», поэтому nextDouble действительно означает «получить следующий токен и попытаться разобрать его как двойной». Токены разделены разделителями, которые по умолчанию являются пробелами. Так что у Scanner не будет проблем с "52 hi", но он не будет работать с "52hi". Теоретически, разделителем может быть любое регулярное выражение, которое я выберу, поэтому все, что мне нужно сделать, это составить регулярное выражение, которое при совпадении означает конец числа. Но это кажется даже труднее сделать, чем непосредственно писать регулярное выражение.

Я собирался расстаться с надеждой, когда нашел java .text.DecimalFormat, который явно говорит: «Я буду анализировать, насколько я может, и я скажу вам, как далеко я продвинулся, чтобы вы могли продолжать делать что-то еще с этого момента ". Но кажется, что он был в первую очередь предназначен для форматирования вещей для потребления человеком и, возможно, для анализа вещей, написанных машинами, а не для анализа вещей, написанных людьми, и это проявляется множеством маленьких способов. Например, он «поддерживает» нотацию scientifi c, например «1.2e1», но если вы используете ее, он будет настаивать на том, что число должно быть в нотации scientifi c, и не сможет выполнить синтаксический анализ, если вместо этого вы введете «12». Можно попытаться обойти эту проблему, проверив место, где произошел сбой, и проанализировав только материал до этого как число, но это подвержено ошибкам и даже более раздражает, чем просто написание регулярного выражения для чисел с плавающей точкой.

Тем временем в C, это будет просто sscanf ("% f"), и в C ++ вы можете использовать поток строк, чтобы делать в основном то же самое. Неужели нет эквивалента в Java?

1 Ответ

4 голосов
/ 27 февраля 2020

Документация для Double.valueOf(String) на самом деле включает в себя регулярное выражение, которое можно использовать для проверки, является ли строка double.

Вот оно, без комментариев:

final String Digits     = "(\\p{Digit}+)";
final String HexDigits  = "(\\p{XDigit}+)";
final String Exp        = "[eE][+-]?"+Digits;
final String fpRegex    =
        ("[\\x00-\\x20]*"+
                "[+-]?(" +
                "NaN|"+
                "Infinity|" +
                "((("+Digits+"(\\.)?("+Digits+"?)("+Exp+")?)|"+
                "(\\.("+Digits+")("+Exp+")?)|"+
                "((" +
                "(0[xX]" + HexDigits + "(\\.)?)|" +
                "(0[xX]" + HexDigits + "?(\\.)" + HexDigits + ")" +
                ")[pP][+-]?" + Digits + "))" +
                "[fFdD]?))" +
                "[\\x00-\\x20]*");

Вы можете использовать это так:

Matcher m = Pattern.compile(fpRegex).matcher(input);
if (m.find()) {
    String doublePartOnly = m.group();
}

В ходе некоторых базовых c испытаний я обнаружил, что регулярное выражение является жадным, поэтому оно будет соответствовать 1.2e1 в 1.2e1hello, в отличие от 1.2.

...