Подтвердить что ты не робот

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

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

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

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

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

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

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

4b9b3361

Ответ 1

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

Ответ 2

Вы можете указать требуемый локаль:

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

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

Ответ 3

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

Ответ 4

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

Из статьи:

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

  • Получите соответствующий NumberFormat и определите переменную ParsePosition.
  • Установите индекс ParsePosition равным нулю.
  • Разбираем входное значение с помощью синтаксического анализа (String source, ParsePosition parsePosition).
  • Выполните операции с ошибками, если входная длина и значение индекса ParsePosition не совпадают или если разобранный номер равен нулю.
  • В противном случае значение прошло проверку.

Ответ 5

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

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

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

Что означает число 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 для предоставления желаемого формата вывода... Этот вид заботится о локализации, но опять-таки нужно сделать больше работы для поддержки "," для десятичных знаков...

Ответ 6

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

В любом случае, я думаю, что вы можете использовать недостатки формата Number в своих интересах. Поскольку у вас нет чего-то вроде "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;
    }
} 

Как вы думаете?

Ответ 7

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

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

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

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

Ответ 8

Я понимаю, что вы хотите охватить западные/латинские языки, сохраняя при этом как можно более строгую интерпретацию. Итак, что я здесь делаю, попросите 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;
}

}

Ответ 9

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

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

Ответ 10

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

Ответ 11

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

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

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

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

3) Вызовите parse или isNumber.

Ответ 12

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

Ответ 13

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

Ответ 14

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

Ответ 15

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

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

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

Редактировать 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 = '\0';
    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       
}