La mejor manera de analizar ¿Doble con una coma como separador decimal?

148

Lo siguiente resulta en un Exception:

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

¿Hay una mejor manera de analizar "1,234"para obtener 1.234que p = p.replaceAll(",",".");:?

Koerr
fuente
17
En mi experiencia, replaceAll (), como sugirió, es la mejor manera de hacer esto. No depende de la configuración regional actual, es simple y funciona.
Joonas Pulakka
1
@Marco Altieri: replaceAll(",",".")reemplaza todas las comas con puntos. Si no hay comas, entonces no hace nada. Double.valueOf()funciona (solo) con cadenas que usan punto como separador decimal. Nada aquí se ve afectado por la configuración regional predeterminada actual. docs.oracle.com/javase/8/docs/api/java/lang/…
Joonas Pulakka
44
El único problema replaceAll(",",".")es que solo funcionará si hay una coma: es decir: 1,234,567 arrojará java.lang.NumberFormatException: multiple points. Una expresión regular con anticipación positiva será suficiente p.replaceAll(",(?=[0-9]+,)", "").replaceAll(",", ".")Más en: regular-expressions.info/lookaround.html
artemisian
2
No hay ningún problema. NumberFormatException es bueno. ¿Cómo puede saber qué coma es la correcta? El formato es incorrecto y todo lo que puede hacer es mostrar un mensaje mejor legible que la excepción para el usuario.
El increíble Jan
2
@ TheincredibleJan No, el formato no es incorrecto. Algunos entornos locales usan comas como separador de miles, por lo que puede tener más de uno en un número y técnicamente sigue siendo una entrada válida.
Vratislav Jindra

Respuestas:

206

Utilice java.text.NumberFormat :

NumberFormat format = NumberFormat.getInstance(Locale.FRANCE);
Number number = format.parse("1,234");
double d = number.doubleValue();
dogbane
fuente
11
Esto solo funciona si la configuración regional predeterminada actual utiliza una coma como separador decimal.
Joonas Pulakka
66
Para complicar aún más las cosas, algunos entornos locales usan comas como separador de miles , en cuyo caso "1,234" se analizaría a 1234.0 en lugar de arrojar un error.
Joonas Pulakka
17
El problema con NumberFormat es que ignorará silenciosamente los caracteres no válidos. Por lo tanto, si intenta analizar "1,23abc", felizmente devolverá 1,23 sin indicarle que la cadena entregada contenía caracteres que no se pueden analizar. En algunas situaciones, eso podría ser realmente deseable, pero no creo que sea el comportamiento deseado.
E-Riz
77
para TURQUÍA, debe usar NumberFormat.getInstance (new Locale (tr_TR))
Günay Gültekin el
2
para saber quién usa qué separador ver en.wikipedia.org/w/…
fiffy
67

Puede usar esto (la configuración regional en francés tiene ,un separador decimal)

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

O puede usar java.text.DecimalFormaty establecer los símbolos apropiados:

DecimalFormat df = new DecimalFormat();
DecimalFormatSymbols symbols = new DecimalFormatSymbols();
symbols.setDecimalSeparator(',');
symbols.setGroupingSeparator(' ');
df.setDecimalFormatSymbols(symbols);
df.parse(p);
Bozho
fuente
Sí ... si no configuramos el separador de agrupamiento de mil y solo usamos el formato francés, un número en formato español (1.222.222,33) se convertirá a "1 222 222,33", que no es lo que quiero . ¡Así que gracias!
WesternGun
1
Otra cosa es que la configuración regional española no aparece como "predeterminada" y no puedo construir una Localecon el formato correcto new Locale("es", "ES")y luego analizar automáticamente la cadena de números con NumberFormat, ,como separador decimal y .como separador de mil grupos. Solo DecimalFormatfunciona.
WesternGun
¿Por qué no todos los países están disponibles allí? Me siento raro por usar el idioma francés para formatear números polacos ...
Línea del
18

Como señala E-Riz, NumberFormat.parse (String) analiza "1,23abc" como 1,23. Para tomar toda la 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
fuente
2
Esta estrategia se explica en detalle aquí: ibm.com/developerworks/library/j-numberformat
Janus Varmarken
7
Double.parseDouble(p.replace(',','.'))

... es muy rápido, ya que busca en la matriz de caracteres subyacente char-by-char. Las versiones de reemplazo de cadenas compilan un RegEx para evaluar.

Básicamente, reemplazar (char, char) es aproximadamente 10 veces más rápido y dado que harás este tipo de cosas en código de bajo nivel, tiene sentido pensar en esto. El optimizador de Hot Spot no lo resolverá ... Ciertamente no lo hace en mi sistema.

CoheteBanana
fuente
4

Si no conoce la configuración regional correcta y la cadena puede tener mil separadores, este podría ser el ú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(",", "."));

Tenga en cuenta: esto felizmente analizará cadenas como "R 1 52.43,2" a "15243.2".

usuario3506443
fuente
4

Este es el método estático que uso en mi propio 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
fuente
55
¿Qué sucede si la configuración regional predeterminada es algo similar al alemán, donde una coma denota un lugar decimal? Podría pasar, por ejemplo, "1,000,000" que no se analizaría en la configuración regional alemana y luego sería reemplazado por "1,000,000" que no es un Double válido.
Eddie Curtis
Hola @jimmycar, acabo de actualizar mi respuesta para usar la versión actual de mi método estático. ¡Espero que esto resuelva tu problema! Pete
Pete
1

Por supuesto, debe usar la configuración regional correcta. Esta pregunta te ayudará.

kgiannakakis
fuente
0

En el caso de que no conozca la configuración regional del valor de cadena recibido y no sea necesariamente la misma configuración regional que la configuración regional predeterminada actual, puede usar esto:

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

}

Devolverá un doble sin importar cuál sea la configuración regional de la cadena. Y no importa cuántas comas o puntos haya. Por lo tanto, pasar 1,000,000.54funcionará, 1.000.000,54así que no tendrá que depender de la configuración regional predeterminada para analizar la cadena más. El código no está tan optimizado como puede ser, por lo que cualquier sugerencia es bienvenida. Traté de probar la mayoría de los casos para asegurarme de que resuelve el problema, pero no estoy seguro de que cubra todos. Si encuentra un valor de ruptura, hágamelo saber.

Amro elaswar
fuente
-5

Esto haría el trabajo:

Double.parseDouble(p.replace(',','.')); 
Helge
fuente
66
La pregunta inicial decía "¿Hay una mejor manera de analizar" 1,234 "para obtener 1,234 que: p = p.replaceAll (", ",". ");" , si cree que replacedifiere significativamente del uso replaceAll, explique por qué.
SuperBiasedMan