Определить, является ли строка числом и преобразовать в Java?

Я знаю, что варианты этого вопроса часто задавались раньше (см. здесь и здесь например), но это не an точно повторяющиеся из них.

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

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

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

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

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

15 ответов


звучит довольно странно, но я бы попытался следовать ответ и использовать 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 может использоваться для изменения на нужный язык.


вы можете указать Локаль, которая вам нужна:

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

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


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


К Сожалению Двухместный.parseDouble(s) или new 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");
  }
}

EDIT: очевидно, это должно быть расширено для признания научной нотации, но это должно быть достаточно просто, тем более, что вам не нужно ничего проверять после e, вы можете просто позволить parseDouble потерпеть неудачу, если это плохо образующийся.

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


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

из статьи:

подводя итог, шаги для правильной обработки ввода:

  1. получите соответствующий NumberFormat и определите переменную ParsePosition.
  2. установить указатель ParsePosition к нулю.
  3. проанализируйте входное значение с помощью parse(string source, ParsePosition parsePosition).
  4. выполнять операции с ошибками, если входная длина и значение индекса Парсепозиции не совпадают или если анализируемое число равно null.
  5. в противном случае значение прошло проверку.

Это интересная проблема. Но, может быть, он немного открыт? Вы ищете специально, чтобы идентифицировать номера базы-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;
    }
} 

что вы думаете?


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

1) Проверьте научную нотацию (соответствует ли она шаблону быть всеми числами, запятыми, периодами, -/+ и иметь в нем "e"?) -- если это так, разберите, как вы хотите

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

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


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

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

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

в Германии это все еще отклоняет "1А"

Он принимает " 1,024.00", но интерпретирует его правильно как 1.024. Аналогично, он принимает "127.100.100.100" как 127100100100.0

на самом деле, немецкий язык правильно идентифицирует и анализирует "1,47 E-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;
}

}

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

   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);
   }
   }

Если вы правильно установили языковой стандарт, встроенный parseDouble совместимость с запятыми. Пример:здесь.


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

1) Определите разделители decimal и группировки. Возможно, потребуется определить другие символы формата (например, индикаторы научной нотации).

http://download.oracle.com/javase/1.4.2/docs/api/java/text/DecimalFormat.html#getDecimalFormatSymbols()

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

3) вызов parse или isNumber.


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


Если вы хотите преобразовать некоторое строковое число, которое разделено запятыми десятичной в двойную, вы можете использовать DecimalSeparator + DecimalFormalSymbols:

final double strToDouble(String str, char separator){
    DecimalFormatSymbols s = new DecimalFormatSymbols();
    s.setDecimalSeparator(separator);
    DecimalFormat df = new DecimalFormat();

    double num = 0;
    df.setDecimalFormatSymbols(s);
    try{
        num = ((Double) df.parse(str)).doubleValue();
    }catch(ClassCastException | ParseException ex){
        // if you want, you could add something here to 
        // indicate the string is not double
    }  
    return num;
}

ну, давайте протестируем его:

    String a = "1.2";
    String b = "2,3";
    String c = "A1";
    String d = "127.0.0.1";

    System.out.println("\"" + a + "\" = " + strToDouble(a, ','));
    System.out.println("\"" + a + "\" (with '.' as separator) = " 
            + strToDouble(a, '.'));
    System.out.println("\"" + b + "\" = " + strToDouble(b, ','));
    System.out.println("\"" + c + "\" = " + strToDouble(c, ','));
    System.out.println("\"" + d + "\" = " + strToDouble(d, ','));

Если вы запустите приведенный выше код, вы увидите:

"1.2" = 0.0
"1.2" (with '.' as separator) = 1.2
"2,3" = 2.3
"A1" = 0.0
"127.0.0.1" = 0.0

Это займет строку, подсчитает ее десятичные и запятые, удалит запятые,сохранит допустимый десятичный (обратите внимание, что это основано на стандартизации США - для обработки 1.000.000, 00 как 1 миллион этот процесс должен будет иметь десятичную и запятую обработку), определить, является ли структура допустимой, а затем вернуть double. Возвращает null, если строка не может быть преобразована. редактировать: добавлена поддержка международных или США. 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;
}

этот код должен обрабатывать большинство входных данных, кроме IP-адресов, где все группы цифр находятся в трех (например: 255.255.255.255 допустимо, но не 255.1.255.255). Он также не поддерживает научную нотацию

Он будет работать с большинством вариантов сепараторов (",", ". или космос). Если обнаружено более одного разделителя, первым считается разделитель тысяч с дополнительными проверками (действительность и т. д.)

Edit: prevDigit используется для проверки того, что число использует тысячи разделителей правильно. Если существует более одной группы из тысяч, все, кроме первой, должны быть в группах по 3. Я изменил код, чтобы сделать его более ясным, так что "3" - это не магическое число, но постоянный.

Edit 2: Я не против голосов много, но может кто-нибудь объяснить, в чем проблема?

/* A number using thousand separator must have
   groups of 3 digits, except the first one.
   Numbers following the decimal separator can
   of course be unlimited. */
private final static int GROUP_SIZE=3;

public static boolean isNumber(String input) {
    boolean inThousandSep = false;
    boolean inDecimalSep = false;
    boolean endsWithDigit = false;
    char thousandSep = '';
    int prevDigits = 0;

    for(int i=0; i < input.length(); i++) {
        char c = input.charAt(i);

        switch(c) {
            case ',':
            case '.':
            case ' ':
                endsWithDigit = false;
                if(inDecimalSep)
                    return false;
                else if(inThousandSep) {
                    if(c != thousandSep)
                        inDecimalSep = true;
                    if(prevDigits != GROUP_SIZE)
                        return false; // Invalid use of separator
                }
                else {
                    if(prevDigits > GROUP_SIZE || prevDigits == 0)
                        return false;
                    thousandSep = c;
                    inThousandSep = true;
                }
                prevDigits = 0;
                break;

            default:
                if(Character.isDigit(c)) {
                    prevDigits++;
                    endsWithDigit = true;
                }
                else {
                    return false;
                }
        }
    }
    return endsWithDigit;
}

тестовый код:

public static void main(String[] args) {
    System.out.println(isNumber("100"));               // true
    System.out.println(isNumber("100.00"));            // true
    System.out.println(isNumber("1,5"));               // true
    System.out.println(isNumber("1,000,000.00."));     // false
    System.out.println(isNumber("100,00,2"));          // false
    System.out.println(isNumber("123.123.23.123"));    // false
    System.out.println(isNumber("123.123.123.123"));   // true       
}