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


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

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

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

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.


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".


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

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


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.


-5

Isso faria o trabalho:

Double.parseDouble(p.replace(',','.')); 

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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.