Определить, является ли String числом, и конвертировать в Java? - PullRequest
24 голосов
/ 04 мая 2011

Я знаю, что варианты этого вопроса часто задавались ранее (см., Например, здесь и здесь ), но это не и точно дубликат этих.

Я хотел бы проверить, является ли String число, и если да, то я бы хотел сохранить его как double.Есть несколько способов сделать это, но все они кажутся неподходящими для моих целей.

Одним из решений было бы использование Double.parseDouble(s) или аналогично new BigDecimal(s).Тем не менее, эти решения не работают, если присутствуют запятые (поэтому «1234» может вызвать исключение).Конечно, я мог бы удалить все запятые, прежде чем использовать эти методы, но, похоже, это создаст массу проблем в других локалях.

Я смотрел на Apache Commons NumberUtils.isNumber(s), но у него такая же проблема с запятой.

Я считал NumberFormat или DecimalFormat, но они казались слишком снисходительными.Например, «1A» форматируется как «1» вместо того, чтобы указывать, что это не число.Кроме того, что-то вроде «127.0.0.1» будет засчитываться как число 127 вместо того, чтобы указывать, что это не число.

Я чувствую, что мои требования не настолько экзотичны, что я первым делаю это, но ни одно из решений не делает именно то, что мне нужно.Я предполагаю, что даже я не знаю точно , что мне нужно (в противном случае я мог бы написать свой собственный анализатор), но я знаю, что вышеупомянутые решения не работают по указанным причинам.Существует ли какое-либо решение или мне нужно точно определить, что мне нужно, и написать для него собственный код?

Ответы [ 15 ]

15 голосов
/ 08 февраля 2012

Звучит довольно странно, но я постараюсь следовать этому ответу и использовать java.util.Scanner.

Scanner scanner = new Scanner(input);
if (scanner.hasNextInt())
    System.out.println(scanner.nextInt());
else if (scanner.hasNextDouble())
    System.out.println(scanner.nextDouble());
else
    System.out.println("Not a number");

Для таких входов, как 1A, 127.0.0.1, 1,234, 6.02e-23, я получаю следующий вывод:

Not a number
Not a number
1234
6.02E-23

Scanner.useLocale можно использовать для перехода к желаемой локали.

4 голосов
/ 07 февраля 2012

Вы можете использовать ParsePosition для проверки полного использования строки в операции NumberFormat.parse. Если строка используется, то у вас нет ситуации "1A". Если нет, вы делаете и можете вести себя соответственно. См. здесь для краткого описания решения и здесь для связанной ошибки JDK, которая закрывается как исправление из-за опции ParsePosition.

4 голосов
/ 07 февраля 2012

Вы можете указать нужный языковой стандарт:

NumberFormat nf = NumberFormat.getInstance(Locale.GERMAN);
double myNumber = nf.parse(myString).doubleValue();

Это должно работать в вашем примере, так как в немецкой локали запятая используется в качестве десятичного разделителя.

3 голосов
/ 12 февраля 2012

Лучше всего делать это вручную.Выясните, что вы можете принять как число, и не обращайте внимания на все остальное:

   import java.lang.NumberFormatException;
   import java.util.regex.Pattern;
   import java.util.regex.Matcher;

   public class ParseDouble {
   public static void main(String[] argv) {

       String line = "$$$|%|#|1A|127.0.0.1|1,344|95|99.64";

       for (String s : line.split("\\|")) {
           try {
               System.out.println("parsed: " + 
               any2double(s)
                       );

           }catch (NumberFormatException ne) {
               System.out.println(ne.getMessage());
           }
       }   
   }
   public static double any2double(String input) throws NumberFormatException {

       double out =0d;

       Pattern special         = Pattern.compile("[^a-zA-Z0-9\\.,]+");
       Pattern letters         = Pattern.compile("[a-zA-Z]+");
       Pattern comma           = Pattern.compile(",");
       Pattern allDigits       = Pattern.compile("^[0-9]+$");
       Pattern singleDouble    = Pattern.compile("^[0-9]+\\.[0-9]+$");

       Matcher[] goodCases = new Matcher[]{
           allDigits.matcher(input),
           singleDouble.matcher(input)
       };           

       Matcher[] nanCases = new Matcher[]{
           special.matcher(input),
           letters.matcher(input)
       };


       // maybe cases 
       if (comma.matcher(input).find()){
           out = Double.parseDouble( 
               comma.matcher(input).replaceFirst("."));
           return out;

       }

       for (Matcher m : nanCases) {
           if (m.find()) {
               throw new NumberFormatException("Bad input "+input);
           }
       }

       for (Matcher m : goodCases) {

           if (m.find()) {
               try {
                   out = Double.parseDouble(input);
                   return out;
               } catch (NumberFormatException ne){
                   System.out.println(ne.getMessage());
               }
           }
       }
       throw new NumberFormatException("Could not parse "+input);
   }
   }
3 голосов
/ 12 февраля 2012

Насколько я понимаю, вы хотите охватить западные / латинские языки, сохраняя при этом как можно более строгую интерпретацию.Итак, что я здесь делаю, так это спрашиваю DecimalFormatSymbols, чтобы я сказал, что такое разделители группирования, десятичного, отрицательного и нулевого, и заменяя их на символы, которые распознает Double.

Как это работает?

В США он отклоняет: "1A", "127.100.100.100" и принимает "1.47E-9"

В Германии он по-прежнему отклоняет "1A"

ПРИНИМАЕТ «1 024,00», но правильно интерпретирует как 1,024.Аналогично, он принимает «127.100.100.100» как 127100100100.0

На самом деле, немецкий язык правильно идентифицирует и анализирует «1,47E-9»

Дайте мне знать, если у вас возникли проблемы вдругой язык.

import java.util.Locale;
import java.text.DecimalFormatSymbols;

public class StrictNumberFormat {

public static boolean isDouble(String s, Locale l) {
    String clean = convertLocaleCharacters(s,l);

    try {
        Double.valueOf(clean);
        return true;
    } catch (NumberFormatException nfe) {
        return false;
    }
}

public static double doubleValue(String s, Locale l) {
    return Double.valueOf(convertLocaleCharacters(s,l));
}

public static boolean isDouble(String s) {
    return isDouble(s,Locale.getDefault());
}

public static double doubleValue(String s) {
    return doubleValue(s,Locale.getDefault());
}

private static String convertLocaleCharacters(String number, Locale l) {
    DecimalFormatSymbols symbols = new DecimalFormatSymbols(l);
    String grouping = getUnicodeRepresentation( symbols.getGroupingSeparator() );
    String decimal = getUnicodeRepresentation( symbols.getDecimalSeparator() );
    String negative = getUnicodeRepresentation( symbols.getMinusSign() );
    String zero = getUnicodeRepresentation( symbols.getZeroDigit() );

    String clean = number.replaceAll(grouping, "");
    clean = clean.replaceAll(decimal, ".");
    clean = clean.replaceAll(negative, "-");
    clean = clean.replaceAll(zero, "0");

    return clean;
}

private static String getUnicodeRepresentation(char ch) {
    String unicodeString = Integer.toHexString(ch); //ch implicitly promoted to int
    while(unicodeString.length()<4) unicodeString = "0"+unicodeString;

    return "\\u"+unicodeString;
}

}
3 голосов
/ 11 февраля 2012

Это действительно интересно, и я думаю, что люди пытаются это усложнить.Я бы на самом деле просто разбил это по правилам:

1) Проверить наличие научной записи (соответствует ли она порядку, состоящему из всех чисел, запятых, точек, - / + и содержащему в себе «е»?)- если это так, проанализируйте, как хотите

2) Соответствует ли оно регулярному выражению для допустимых числовых символов (0-9,. - +) (только 1. - или + разрешено), если это так, удалите всеэто не цифра, и анализируйте соответственно, иначе не получится.

Я не вижу ярлыка, который будет работать здесь, просто используйте подход грубой силы, не все в программировании может быть (или должно быть) полностьюэлегантный.

3 голосов
/ 11 февраля 2012

Это интересная проблема. Но, может быть, это немного открытый? Вы ищете специально для обозначения цифр base-10, или шестнадцатеричных, или как? Я предполагаю, база-10. Как насчет валюты? Это важно? Или это просто цифры.

В любом случае, я думаю, что вы можете использовать недостатки числового формата в ваших интересах. Поскольку вы не знаете, что что-то вроде «1A» будет интерпретироваться как 1, почему бы не проверить результат, отформатировав его и сравнив с исходной строкой?

public static boolean isNumber(String s){
    try{
        Locale l = Locale.getDefault();
        DecimalFormat df = new DecimalFormat("###.##;-##.##");
        Number n = df.parse(s);
        String sb = df.format(n);
        return sb.equals(s);
    }
    catch(Exception e){
        return false;
    }
} 

Что ты думаешь?

3 голосов
/ 10 февраля 2012

К сожалению, Double.parseDouble (s) или новые BigDecimal (s) кажутся вашими лучшими вариантами.

Вы цитируете проблемы локализации, но, к сожалению, в любом случае нет надежной поддержки всех локалей без спецификации пользователя. Это просто невозможно.

Иногда вы можете рассуждать об используемой схеме, посмотрев, используются ли вначале запятые или точки, если используются оба, но это не всегда возможно, так зачем даже пытаться? Лучше иметь систему, которая, как вы знаете, надежно работает в определенных ситуациях, чем пытаться полагаться на систему, которая может работать в большем количестве ситуаций, но также может давать плохие результаты ...

Что означает число 123 456? 123456 или 123,456?

Просто удалите запятые, пробелы или точки в зависимости от локали, указанной пользователем. По умолчанию для удаления пробелов и запятых. Если вы хотите сделать его более строгим, то удаляйте только запятые ИЛИ пробелы, а не оба, и только до того периода, если он есть. Также должно быть довольно легко проверить вручную, правильно ли они расположены в тройках. На самом деле пользовательский парсер может быть проще всего здесь.

Вот немного подтверждения концепции. Это немного (очень) грязно, но я считаю, что это работает, и вы все равно поймете идею:).

public class StrictNumberParser {
  public double parse(String numberString) throws NumberFormatException {
    numberString = numberString.trim();
    char[] numberChars = numberString.toCharArray();

    Character separator = null;
    int separatorCount = 0;
    boolean noMoreSeparators = false;
    for (int index = 1; index < numberChars.length; index++) {
      char character = numberChars[index];

      if (noMoreSeparators || separatorCount < 3) {
        if (character == '.') {
          if (separator != null) {
            throw new NumberFormatException();
          } else {
            noMoreSeparators = true;
          }
        } else if (separator == null && (character == ',' || character == ' ')) {
          if (noMoreSeparators) {
            throw new NumberFormatException();
          }
          separator = new Character(character);
          separatorCount = -1;
        } else if (!Character.isDigit(character)) {
          throw new NumberFormatException();
        }

        separatorCount++;
      } else {
        if (character == '.') {
          noMoreSeparators = true;
        } else if (separator == null) {
          if (Character.isDigit(character)) {
            noMoreSeparators = true;
          } else if (character == ',' || character == ' ') {
            separator = new Character(character);
          } else {
            throw new NumberFormatException();
          }
        } else if (!separator.equals(character)) {
          throw new NumberFormatException();
        }

        separatorCount = 0;
      }
    }

    if (separator != null) {
      if (!noMoreSeparators && separatorCount != 3) {
        throw new NumberFormatException();
      }
      numberString = numberString.replaceAll(separator.toString(), "");
    }

    return Double.parseDouble(numberString);
  }

  public void testParse(String testString) {
    try {
      System.out.println("result: " + parse(testString));
    } catch (NumberFormatException e) {
      System.out.println("Couldn't parse number!");
    }
  }

  public static void main(String[] args) {
    StrictNumberParser p = new StrictNumberParser();
    p.testParse("123 45.6");
    p.testParse("123 4567.8");
    p.testParse("123 4567");
    p.testParse("12 45");
    p.testParse("123 456 45");
    p.testParse("345.562,346");
    p.testParse("123 456,789");
    p.testParse("123,456,789");
    p.testParse("123 456 789.52");
    p.testParse("23,456,789");
    p.testParse("3,456,789");
    p.testParse("123 456.12");
    p.testParse("1234567.8");
  }
}

РЕДАКТИРОВАТЬ: очевидно, что это должно быть расширено для распознавания научной нотации, но это должно быть достаточно просто, особенно если вам не нужно фактически проверять что-либо после e, вы можете просто позволить parseDouble fail, если он плохо сформирован .

Также может быть хорошей идеей правильно расширить NumberFormat с этим. иметь getSeparator () для разобранных чисел и setSeparator для предоставления желаемого выходного формата ... Этот вид заботится о локализации, но опять же нужно проделать дополнительную работу для поддержки ',' для десятичных дробей ...

3 голосов
/ 09 февраля 2012

Не уверен, что он соответствует всем вашим требованиям, но найденный код здесь может указать вам верное направление?

Из статьи:

Подводя итог, необходимо выполнить следующие шаги для правильной обработки ввода:

  1. Получить соответствующий NumberFormat и определить переменную ParsePosition.
  2. Установить индекс ParsePosition равным нулю.
  3. Parseвходное значение с синтаксическим анализом (String source, ParsePosition parsePosition).
  4. Выполнение операций с ошибками, если входная длина и значение индекса ParsePosition не совпадают или если проанализированный номер равен нулю.
  5. В противном случае,значение прошло проверку.
1 голос
/ 11 февраля 2012

Это займет строку, посчитает ее десятичные и запятые, удалит запятые, сохранит действительный десятичный знак (обратите внимание, что это основано на стандартизации США - для обработки 1.000.000,00 как 1 миллиона этот процесс должен иметь обработка десятичной запятой и запятой переключается), определить, является ли структура действительной, а затем вернуть double. Возвращает ноль, если строка не может быть преобразована. Редактировать : Добавлена ​​поддержка международных или США. convertStoD (string, true) для США, convertStoD (string, false) для не США. Комментарии теперь для американской версии.

public double convertStoD(string s,bool isUS){
 //string s = "some string or number, something dynamic";
 bool isNegative = false;
 if(s.charAt(0)== '-')
 {
  s = s.subString(1);
  isNegative = true;
 }
 string ValidNumberArguements = new string();
 if(isUS)
 {
   ValidNumberArguements = ",.";
 }else{
   ValidNumberArguements = ".,";
 }
 int length = s.length;
 int currentCommas = 0;
 int currentDecimals = 0;
 for(int i = 0; i < length; i++){
  if(s.charAt(i) == ValidNumberArguements.charAt(0))//charAt(0) = ,
  {
   currentCommas++;
   continue;
  }
  if(s.charAt(i) == ValidNumberArguements.charAt(1))//charAt(1) = .
  {
   currentDec++;
   continue;
  }
  if(s.charAt(i).matches("\D"))return null;//remove 1 A
 }
 if(currentDecimals > 1)return null;//remove 1.00.00
 string decimalValue = "";
 if(currentDecimals > 0)
 {
   int index = s.indexOf(ValidNumberArguements.charAt(1));
   decimalValue += s.substring(index);
   s = s.substring(0,index);
   if(decimalValue.indexOf(ValidNumberArguements.charAt(0)) != -1)return null;//remove 1.00,000
 }
 int allowedCommas = (s.length-1) / 3;
 if(currentCommas > allowedCommas)return null;//remove 10,00,000
 String[] NumberParser = s.split(ValidNumberArguements.charAt(0));
 length = NumberParser.length;
 StringBuilder returnString = new StringBuilder();
 for(int i = 0; i < length; i++)
 {
   if(i == 0)
   {
     if(NumberParser[i].length > 3 && length > 1)return null;//remove 1234,0,000
     returnString.append(NumberParser[i]);
     continue;
   }
   if(NumberParser[i].length != 3)return null;//ensure proper 1,000,000
   returnString.append(NumberParser[i]);
 }
 returnString.append(decimalValue);
 double answer = Double.parseDouble(returnString);
 if(isNegative)answer *= -1;
 return answer;
}
...