Melhor maneira de analisarDuplo com vírgula como separador decimal?

148

A seguir está resultando em um Exception:

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

Existe uma maneira melhor de analisar "1,234"para obter do 1.234que p = p.replaceAll(",",".");:?

Koerr
fonte
17
Na minha experiência, replaceAll (), como você sugeriu, é a melhor maneira de fazer isso. Não depende da localidade atual, é simples e funciona.
Joonas Pulakka
1
@ Marco Altieri: replaceAll(",",".")substitui todas as vírgulas por pontos. Se não houver vírgulas, não fará nada. Double.valueOf()funciona (apenas) com cadeias que usam ponto como separador decimal. Nada aqui é afetado pela localidade padrão atual. docs.oracle.com/javase/8/docs/api/java/lang/…
Joonas Pulakka
4
O único problema replaceAll(",",".")é que só funcionará se houver uma única vírgula: ie: 1.234.567 será lançada java.lang.NumberFormatException: multiple points. A regex com antecipação positiva será suficiente p.replaceAll(",(?=[0-9]+,)", "").replaceAll(",", ".")Mais em: regular-expressions.info/lookaround.html
Artemisian
2
Não tem problema. O NumberFormatException é bom. Como você pode saber qual vírgula é a correta? O formato está errado e tudo o que você pode fazer é mostrar uma mensagem legível melhor que a exceção para o usuário.
O incrível
2
@TheincredibleJan Não, o formato não está errado. Alguns códigos de idioma usam vírgula como separador de milhares, para que você possa ter mais de um deles em um número e, tecnicamente, ainda é uma entrada válida.
Vratislav Jindra

Respostas:

206

Use java.text.NumberFormat :

NumberFormat format = NumberFormat.getInstance(Locale.FRANCE);
Number number = format.parse("1,234");
double d = number.doubleValue();
dogbane
fonte
11
Isso funciona apenas se o código do idioma padrão atual usar uma vírgula como separador decimal.
Joonas Pulakka
6
Para bagunçar ainda mais as coisas, alguns códigos de idioma usam vírgula como separador de milhares. Nesse caso, "1.234" seria analisado para 1234.0 em vez de gerar um erro.
Joonas Pulakka
17
O problema com NumberFormat é que ele ignorará silenciosamente caracteres inválidos. Portanto, se você tentar analisar "1,23abc", ele retornará 1,23 com prazer, sem indicar que a String passada continha caracteres não analisáveis. Em algumas situações, isso pode ser realmente desejável, mas não acho que esse seja o comportamento desejado.
E-Riz
7
Para a Turquia, você deve usar NumberFormat.getInstance (novo Locale (tr_TR))
Günay Gültekin
2
para quem usa o separador, veja en.wikipedia.org/w/…
fiffy
67

Você pode usar isso (o código de idioma francês possui ,para separador decimal)

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

Ou você pode usar java.text.DecimalFormate definir os símbolos apropriados:

DecimalFormat df = new DecimalFormat();
DecimalFormatSymbols symbols = new DecimalFormatSymbols();
symbols.setDecimalSeparator(',');
symbols.setGroupingSeparator(' ');
df.setDecimalFormatSymbols(symbols);
df.parse(p);
Bozho
fonte
Sim ... se não definirmos o separador de mil agrupamentos e usar apenas o formato francês, um número no formato espanhol (1.222.222,33) será convertido para "1 222 222,33", que não é o que eu quero . Então obrigado!
WesternGun 30/03
1
Outra coisa é que a localidade em espanhol não está listada como "padrão" e não posso construir um Localecom formato correto com new Locale("es", "ES")e, em seguida, analisar automaticamente a sequência numérica com NumberFormat, com ,o separador decimal e .o separador de mil grupos. Só DecimalFormatfunciona.
WesternGun
Por que nem todos os países estão disponíveis lá? Eu me sinto estranho sobre o uso de idioma francês para formatar números poloneses ...
Linha
18

Como E-Riz aponta, NumberFormat.parse (String) analisa "1,23abc" como 1,23. Para pegar toda a entrada, podemos usar:

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
fonte
2
Essa estratégia é explicada em detalhes aqui: ibm.com/developerworks/library/j-numberformat
Janus Varmarken 17/03/15
7
Double.parseDouble(p.replace(',','.'))

... é muito rápido, pois pesquisa a matriz de caracteres subjacente, char a char. A cadeia substitui as versões compila um RegEx para avaliar.

Basicamente, substituir (char, char) é cerca de 10 vezes mais rápido e, como você fará esse tipo de coisa no código de baixo nível, faz sentido pensar nisso. O otimizador do Hot Spot não descobrirá ... Certamente não está no meu sistema.

RocketBanana
fonte
4

Se você não souber o Local correto e a string puder ter um separador de milhar, esse pode ser o último recurso:

    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(",", "."));

Esteja ciente: isso analisará felizmente cadeias de caracteres como "R 1 52.43,2" a "15243.2".

user3506443
fonte
4

Este é o método estático que eu uso no meu próprio código:

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;
        }
    }
}
Pete
fonte
5
E se o Código de idioma padrão for algo como alemão, onde uma vírgula denota uma casa decimal? Você poderia passar, por exemplo, "1.000.000", que não seriam analisados ​​na localidade alemã e depois seriam substituídos por "1.000.000", que não é um duplo válido.
Eddie Curtis
Oi @ jimmycar, Acabei de atualizar minha resposta para usar a versão atual do meu método estático. Espero que isso resolva o seu problema! Pete
Pete
1

Obviamente, você precisa usar o local correto. Esta pergunta vai ajudar.

kgiannakakis
fonte
0

No caso em que você não conhece o código de idioma do valor da string recebido e não é necessariamente o mesmo código de idioma que o código de idioma padrão atual, você pode usar isso:

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

}

Ele retornará um duplo, independentemente do local da string. E não importa quantas vírgulas ou pontos existam. Portanto, a passagem 1,000,000.54funcionará, 1.000.000,54assim você não precisará mais contar com o código do idioma padrão para analisar a sequência. O código não está tão otimizado quanto possível, portanto, todas as sugestões são bem-vindas. Tentei testar a maioria dos casos para garantir que ele resolvesse o problema, mas não tenho certeza de que ele cubra tudo. Se você encontrar um valor baixo, me avise.

Amro elaswar
fonte
-5

Isso faria o trabalho:

Double.parseDouble(p.replace(',','.')); 
Helge
fonte
6
A pergunta inicial dizia: "Existe uma maneira melhor de analisar" 1.234 "para obter 1.234 do que: p = p.replaceAll (", ",". ");" , se você acha que replacedifere significativamente do uso replaceAll, explique o motivo.
precisa saber é o seguinte