Bester Weg zum ParsenDoppeln mit Komma als Dezimaltrennzeichen?

148

Folgendes führt zu einem Exception:

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

Gibt es eine bessere Art und Weise zu analysieren , "1,234"zu erhalten 1.234als: p = p.replaceAll(",",".");?

Koerr
quelle
17
Nach meiner Erfahrung ist replaceAll (), wie Sie vorgeschlagen haben, der beste Weg, dies zu tun. Es hängt nicht vom aktuellen Gebietsschema ab, es ist einfach und es funktioniert.
Joonas Pulakka
1
@Marco Altieri: Ersetzt replaceAll(",",".")alle Kommas durch Punkte. Wenn es keine Kommas gibt, tut es nichts. Double.valueOf()funktioniert (nur) mit Zeichenfolgen, die Punkt als Dezimaltrennzeichen verwenden. Hier ist nichts vom aktuellen Standardgebietsschema betroffen. docs.oracle.com/javase/8/docs/api/java/lang/…
Joonas Pulakka
4
Das einzige Problem dabei replaceAll(",",".")ist, dass es nur funktioniert, wenn es ein einziges Komma gibt: dh: 1.234.567 wird werfen java.lang.NumberFormatException: multiple points. Eine Regex mit positivem Lookahead wird ausreichen. p.replaceAll(",(?=[0-9]+,)", "").replaceAll(",", ".")Weitere Informationen finden Sie unter
Artemisian
2
Es gibt kein Problem. Die NumberFormatException ist gut. Wie können Sie wissen, welches Komma das richtige ist? Das Format ist falsch und Sie können dem Benutzer lediglich eine besser lesbare Nachricht als die Ausnahme anzeigen.
Der unglaubliche
2
@TheincredibleJan Nein, das Format ist nicht falsch. Einige Gebietsschemas verwenden Komma als Tausendertrennzeichen, sodass Sie mehr als eines davon in einer Zahl haben können und es technisch immer noch eine gültige Eingabe ist.
Vratislav Jindra

Antworten:

206

Verwenden Sie java.text.NumberFormat :

NumberFormat format = NumberFormat.getInstance(Locale.FRANCE);
Number number = format.parse("1,234");
double d = number.doubleValue();
Dogbane
quelle
11
Dies funktioniert nur, wenn das aktuelle Standardgebietsschema ein Komma als Dezimaltrennzeichen verwendet.
Joonas Pulakka
6
Um die Dinge weiter durcheinander zu bringen, verwenden einige Gebietsschemas Komma als Tausendertrennzeichen . In diesem Fall würde "1.234" auf 1234.0 analysiert, anstatt einen Fehler auszulösen.
Joonas Pulakka
17
Das Problem mit NumberFormat ist, dass ungültige Zeichen stillschweigend ignoriert werden. Wenn Sie also versuchen, "1,23abc" zu analysieren, wird glücklich 1,23 zurückgegeben, ohne dass Sie darauf hingewiesen werden, dass die übergebene Zeichenfolge nicht analysierbare Zeichen enthält. In einigen Situationen könnte das tatsächlich wünschenswert sein, aber ich denke nicht, dass es normalerweise das gewünschte Verhalten ist.
E-Riz
7
Für die Türkei sollten Sie NumberFormat.getInstance (neues Gebietsschema (tr_TR))
Günay Gültekin
2
Für wen verwendet welcher Separator siehe en.wikipedia.org/w/…
fiffy
67

Sie können dies verwenden (das französische Gebietsschema hat ein ,Dezimaltrennzeichen)

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

Oder Sie können java.text.DecimalFormatdie entsprechenden Symbole verwenden und einstellen:

DecimalFormat df = new DecimalFormat();
DecimalFormatSymbols symbols = new DecimalFormatSymbols();
symbols.setDecimalSeparator(',');
symbols.setGroupingSeparator(' ');
df.setDecimalFormatSymbols(symbols);
df.parse(p);
Bozho
quelle
Ja ... wenn wir das Trennzeichen für tausend Gruppierungen nicht festlegen und nur das französische Format verwenden, wird eine Zahl im spanischen Format (1.222.222,33) in "1 222 222,33" konvertiert, was ich nicht möchte . So danke!
WesternGun
1
Eine andere Sache ist, dass das spanische Gebietsschema nicht als "Standard" aufgeführt ist und ich keine Localemit korrektem Format erstellen und dann die Zahlenzeichenfolge new Locale("es", "ES")automatisch NumberFormatmit ,als Dezimaltrennzeichen und .als Tausendergruppentrennzeichen DecimalFormat
analysieren kann
Warum sind dort nicht alle Länder verfügbar? Ich finde es komisch, das französische Gebietsschema zum Formatieren polnischer Zahlen zu verwenden ...
Zeile
18

Wie E-Riz hervorhebt, analysiert NumberFormat.parse (String) "1,23abc" als 1,23. Um die gesamte Eingabe zu übernehmen, können wir verwenden:

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
quelle
2
Diese Strategie wird hier ausführlich erklärt: ibm.com/developerworks/library/j-numberformat
Janus Varmarken
7
Double.parseDouble(p.replace(',','.'))

... ist sehr schnell, da es das zugrunde liegende Zeichenarray char-by-char durchsucht. Die Versionen zum Ersetzen von Zeichenfolgen kompilieren ein RegEx, das ausgewertet werden soll.

Grundsätzlich ist das Ersetzen (char, char) ungefähr zehnmal schneller und da Sie diese Art von Dingen in Low-Level-Code ausführen, ist es sinnvoll, darüber nachzudenken. Der Hot Spot-Optimierer wird es nicht herausfinden ... Auf meinem System sicherlich nicht.

RocketBanana
quelle
4

Wenn Sie das richtige Gebietsschema nicht kennen und die Zeichenfolge tausend Trennzeichen haben kann, kann dies ein letzter Ausweg sein:

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

Beachten Sie: Dadurch werden Zeichenfolgen wie "R 1 52.43,2" bis "15243.2" gerne analysiert.

user3506443
quelle
4

Dies ist die statische Methode, die ich in meinem eigenen Code verwende:

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
quelle
5
Was ist, wenn das Standardgebietsschema so etwas wie Deutsch ist, wobei ein Komma eine Dezimalstelle angibt? Sie könnten beispielsweise "1.000.000" übergeben, die nicht in das deutsche Gebietsschema analysiert werden und dann durch "1.000.000" ersetzt werden, was kein gültiges Double ist.
Eddie Curtis
Hallo @jimmycar, ich habe gerade meine Antwort aktualisiert, um die aktuelle Version meiner statischen Methode zu verwenden. Ich hoffe das löst dein Problem! Pete
Pete
1

Sie müssen natürlich das richtige Gebietsschema verwenden. Diese Frage wird helfen.

kgiannakakis
quelle
0

In dem Fall, in dem Sie das Gebietsschema des empfangenen Zeichenfolgenwerts nicht kennen und es nicht unbedingt dasselbe Gebietsschema wie das aktuelle Standardgebietsschema ist, können Sie Folgendes verwenden:

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

}

Es wird ein Double zurückgegeben, unabhängig vom Gebietsschema der Zeichenfolge. Und egal wie viele Kommas oder Punkte es gibt. Das Übergeben 1,000,000.54funktioniert also auch, 1.000.000,54sodass Sie sich beim Parsen der Zeichenfolge nicht mehr auf das Standardgebietsschema verlassen müssen. Der Code ist nicht so optimiert wie er sein kann, daher sind Vorschläge willkommen. Ich habe versucht, die meisten Fälle zu testen, um sicherzustellen, dass das Problem dadurch gelöst wird, aber ich bin nicht sicher, ob es alles abdeckt. Wenn Sie einen Bruchwert finden, lassen Sie es mich wissen.

Amro elaswar
quelle
-5

Dies würde den Job machen:

Double.parseDouble(p.replace(',','.')); 
Helge
quelle
6
Die erste Frage lautete : "Gibt es eine bessere Möglichkeit," 1.234 "zu analysieren, um 1.234 zu erhalten, als: p = p.replaceAll (", ",". ");" Wenn Sie der Meinung sind, replacedass sich die Verwendung erheblich von der Verwendung unterscheidet replaceAll, erklären Sie bitte, warum.
SuperBiasedMan