Лучший способ разобрать Double с запятой в качестве десятичного разделителя?

148

Следующее приводит к Exception:

String p="1,234";
Double d=Double.valueOf(p); 
System.out.println(d);

Есть ли лучший способ разобрать, "1,234"чтобы получить, 1.234чем p = p.replaceAll(",",".");:?

Koerr
источник
17
По моему опыту, replaceAll (), как вы предложили, является лучшим способом сделать это. Это не зависит от текущей локали, это просто и работает.
Joonas Pulakka
1
@Marco Altieri: replaceAll(",",".")заменяет все запятые на точки. Если запятых нет, то ничего не происходит. Double.valueOf()работает (только) со строками, которые используют точку в качестве десятичного разделителя. Ничто здесь не зависит от текущей локали по умолчанию. docs.oracle.com/javase/8/docs/api/java/lang/…
Joonas Pulakka
4
Единственная проблема в replaceAll(",",".")том, что он будет работать только при наличии единственной запятой: то есть: 1 234 567 выбрасывает java.lang.NumberFormatException: multiple points. Будет достаточно регулярного выражения с положительным взглядомp.replaceAll(",(?=[0-9]+,)", "").replaceAll(",", ".") Подробнее на: регулярно-экспрессии.info/lookaround.html
artemisian
2
Нет проблем. NumberFormatException это хорошо. Как узнать, какая запятая правильная? Неправильный формат, и все, что вы можете сделать, это показать пользователю более удобочитаемое сообщение, чем исключение.
Невероятный
2
@TheincredibleJan Нет, формат не неправильный. Некоторые локали используют запятую в качестве разделителя тысяч, так что вы можете иметь более одного из них в числе, и это технически все еще допустимый ввод.
Вратислав Джиндра

Ответы:

206

Используйте java.text.NumberFormat :

NumberFormat format = NumberFormat.getInstance(Locale.FRANCE);
Number number = format.parse("1,234");
double d = number.doubleValue();
кендырь
источник
11
Это работает только в том случае, если текущая локаль по умолчанию использует запятую в качестве десятичного разделителя.
Joonas Pulakka
6
Чтобы еще больше запутать ситуацию, некоторые локали используют запятую в качестве разделителя тысяч , и в этом случае «1234» будет анализировать 1234.0 вместо выдачи ошибки.
Joonas Pulakka
17
Проблема с NumberFormat заключается в том, что он будет игнорировать недопустимые символы. Поэтому, если вы попытаетесь проанализировать «1,23abc», он с радостью вернет 1.23, не указав вам, что переданная строка содержит непарсируемые символы. В некоторых ситуациях это может быть действительно желательно, но я не думаю, что это обычно желаемое поведение.
E-Riz
7
для ТУРЦИИ вы должны использовать NumberFormat.getInstance (новая локаль (tr_TR))
Günay Gültekin
2
для того, кто использует какой разделитель, смотрите en.wikipedia.org/w/…
fiffy
67

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

NumberFormat nf = NumberFormat.getInstance(Locale.FRANCE);
nf.parse(p);

Или вы можете использовать java.text.DecimalFormatи установить соответствующие символы:

DecimalFormat df = new DecimalFormat();
DecimalFormatSymbols symbols = new DecimalFormatSymbols();
symbols.setDecimalSeparator(',');
symbols.setGroupingSeparator(' ');
df.setDecimalFormatSymbols(symbols);
df.parse(p);
Bozho
источник
Да ... если мы не установим разделитель группировки тысяч и просто используем французский формат, число в испанском формате (1.222.222,33) будет преобразовано в «1 222 222,33», что не то, что я хочу , Так что спасибо!
WesternGun
1
Другое дело, что испанский языковой стандарт не указан как «по умолчанию», и я не могу создать Localeправильный формат с, new Locale("es", "ES")а затем автоматически проанализировать строку чисел с NumberFormat, ,в качестве десятичного разделителя и .как разделитель групп тысяч. DecimalFormatРаботает только .
WesternGun
Почему не все страны доступны там? Я чувствую себя странно об использовании французского языка для форматирования польских чисел ...
Строка
18

Как указывает E-Riz, NumberFormat.parse (String) анализирует "1,23abc" как 1,23. Чтобы взять весь ввод, мы можем использовать:

public double parseDecimal(String input) throws ParseException{
  NumberFormat numberFormat = NumberFormat.getNumberInstance(Locale.getDefault());
  ParsePosition parsePosition = new ParsePosition(0);
  Number number = numberFormat.parse(input, parsePosition);

  if(parsePosition.getIndex() != input.length()){
    throw new ParseException("Invalid input", parsePosition.getIndex());
  }

  return number.doubleValue();
}
dgolive
источник
2
Эта стратегия подробно объясняется здесь: ibm.com/developerworks/library/j-numberformat
Янус Вармаркен
7
Double.parseDouble(p.replace(',','.'))

... очень быстро, так как он выполняет поиск в массиве символов на основе символьных символов. Строка замены версий компилирует RegEx для оценки.

Обычно замена (char, char) примерно в 10 раз быстрее, и, поскольку вы будете делать подобные вещи в низкоуровневом коде, имеет смысл подумать об этом. Оптимизатор Hot Spot не поймет этого ... Конечно, не в моей системе.

RocketBanana
источник
4

Если вы не знаете правильный Locale и строка может иметь разделитель тысяч, это может быть последним средством:

    doubleStrIn = doubleStrIn.replaceAll("[^\\d,\\.]++", "");
    if (doubleStrIn.matches(".+\\.\\d+,\\d+$"))
        return Double.parseDouble(doubleStrIn.replaceAll("\\.", "").replaceAll(",", "."));
    if (doubleStrIn.matches(".+,\\d+\\.\\d+$"))
        return Double.parseDouble(doubleStrIn.replaceAll(",", ""));
    return Double.parseDouble(doubleStrIn.replaceAll(",", "."));

Имейте в виду: это с радостью проанализирует строки типа «R 1 52.43,2» до «15243.2».

user3506443
источник
4

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

public static double sGetDecimalStringAnyLocaleAsDouble (String value) {

    if (value == null) {
        Log.e("CORE", "Null value!");
        return 0.0;
    }

    Locale theLocale = Locale.getDefault();
    NumberFormat numberFormat = DecimalFormat.getInstance(theLocale);
    Number theNumber;
    try {
        theNumber = numberFormat.parse(value);
        return theNumber.doubleValue();
    } catch (ParseException e) {
        // The string value might be either 99.99 or 99,99, depending on Locale.
        // We can deal with this safely, by forcing to be a point for the decimal separator, and then using Double.valueOf ...
        //http://stackoverflow.com/questions/4323599/best-way-to-parsedouble-with-comma-as-decimal-separator
        String valueWithDot = value.replaceAll(",",".");

        try {
          return Double.valueOf(valueWithDot);
        } catch (NumberFormatException e2)  {
            // This happens if we're trying (say) to parse a string that isn't a number, as though it were a number!
            // If this happens, it should only be due to application logic problems.
            // In this case, the safest thing to do is return 0, having first fired-off a log warning.
            Log.w("CORE", "Warning: Value is not a number" + value);
            return 0.0;
        }
    }
}
Пит
источник
5
Что делать, если языковой стандарт по умолчанию похож на немецкий, где запятая обозначает десятичное место? Вы можете передать, например, «1 000 000», которые не будут разбираться в немецкой локали и затем будут заменены на «1 000 000», что не является действительным двойным числом.
Эдди Кертис
Привет @jimmycar, я только что обновил свой ответ, чтобы использовать текущую версию моего статического метода. Я надеюсь, что это решит вашу проблему! Пит
Пит
1

Вам, конечно, нужно использовать правильную локаль. Этот вопрос поможет.

kgiannakakis
источник
0

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

private static double parseDouble(String price){
    String parsedStringDouble;
    if (price.contains(",") && price.contains(".")){
        int indexOfComma = price.indexOf(",");
        int indexOfDot = price.indexOf(".");
        String beforeDigitSeparator;
        String afterDigitSeparator;
        if (indexOfComma < indexOfDot){
            String[] splittedNumber = price.split("\\.");
            beforeDigitSeparator = splittedNumber[0];
            afterDigitSeparator = splittedNumber[1];
        }
        else {
            String[] splittedNumber = price.split(",");
            beforeDigitSeparator = splittedNumber[0];
            afterDigitSeparator = splittedNumber[1];
        }
        beforeDigitSeparator = beforeDigitSeparator.replace(",", "").replace(".", "");
        parsedStringDouble = beforeDigitSeparator+"."+afterDigitSeparator;
    }
    else {
        parsedStringDouble = price.replace(",", "");
    }

    return Double.parseDouble(parsedStringDouble);

}

Он вернет значение double независимо от того, какой будет строка в строке. И не важно, сколько там запятых или точек. Таким образом, передача 1,000,000.54будет работать, так 1.000.000,54что вам не придется больше полагаться на локаль по умолчанию для анализа строки. Код не так оптимизирован, как может быть, поэтому любые предложения приветствуются. Я пытался протестировать большинство случаев, чтобы убедиться, что это решает проблему, но я не уверен, что она охватывает все. Если вы найдете критическое значение, дайте мне знать.

Амро эласвар
источник
-5

Это сделало бы работу:

Double.parseDouble(p.replace(',','.')); 
Helge
источник
6
В первом вопросе говорилось: «Есть ли лучший способ разобрать« 1,234 », чтобы получить 1,234, чем: p = p.replaceAll («, »,«. »); Если вы считаете, что replaceзначительно отличается от использования replaceAll, пожалуйста, объясните, почему.
SuperBiasedMan