Najlepszy sposób na parsowanieDouble z przecinkiem jako separatorem dziesiętnym?


148

Następujące powoduje Exception:

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

Czy istnieje lepszy sposób do analizowania "1,234"dostać 1.234niż: p = p.replaceAll(",",".");?


17
Z mojego doświadczenia wynika, że ​​metoda replaceAll (), jak sugerowałeś, jest najlepszym sposobem na zrobienie tego. Nie zależy od aktualnych ustawień regionalnych, jest proste i działa.
Joonas Pulakka

1
@Marco Altieri: replaceAll(",",".")zastępuje wszystkie przecinki kropkami. Jeśli nie ma przecinków, nic nie robi. Double.valueOf()działa (tylko) z łańcuchami, które używają kropki jako separatora dziesiętnego. Na nic tutaj nie ma wpływu bieżące domyślne ustawienie regionalne. docs.oracle.com/javase/8/docs/api/java/lang/…
Joonas Pulakka,

4
Jedynym problemem replaceAll(",",".")jest to, że zadziała to tylko wtedy, gdy jest pojedynczy przecinek: np .: 1,234,567 zostanie rzucone java.lang.NumberFormatException: multiple points. Wystarczy wyrażenie regularne z dodatnim wyprzedzeniem p.replaceAll(",(?=[0-9]+,)", "").replaceAll(",", ".")Więcej na: regular-expressions.info/lookaround.html
artemisian

2
Nie ma problemu. NumberFormatException jest dobry. Skąd możesz wiedzieć, który przecinek jest właściwy? Format jest nieprawidłowy i wszystko, co możesz zrobić, to pokazać użytkownikowi bardziej czytelną wiadomość niż wyjątek.
Niesamowity

2
@TheincredibleJan Nie, format jest prawidłowy. Niektóre lokalizacje używają przecinka jako separatora tysięcy, więc możesz mieć więcej niż jeden z nich w liczbie i technicznie nadal jest to poprawne dane wejściowe.
Vratislav Jindra

Odpowiedzi:


206

Użyj java.text.NumberFormat :

NumberFormat format = NumberFormat.getInstance(Locale.FRANCE);
Number number = format.parse("1,234");
double d = number.doubleValue();

11
Działa to tylko wtedy, gdy obecne domyślne ustawienia regionalne używają przecinka jako separatora dziesiętnego.
Joonas Pulakka

6
Aby jeszcze bardziej zepsuć sprawę , niektóre lokalizacje używają przecinka jako separatora tysięcy , w którym to przypadku "1,234" przeanalizowałoby do 1234.0 zamiast zgłaszać błąd.
Joonas Pulakka

17
Problem z NumberFormat polega na tym, że po cichu ignoruje nieprawidłowe znaki. Więc jeśli spróbujesz przeanalizować "1,23abc", z radością zwróci to 1.23 bez wskazania ci, że przekazany ciąg zawierał znaki, których nie można przeanalizować. W niektórych sytuacjach może to być pożądane, ale nie sądzę, że jest to zwykle pożądane zachowanie.
E-Riz

7
w przypadku TURCJI należy użyć NumberFormat.getInstance (new Locale (tr_TR))
Günay Gültekin

2
kto używa tego separatora, patrz en.wikipedia.org/w/ ...
fiffy

67

Możesz tego użyć (francuskie ustawienia regionalne mają ,separator dziesiętny)

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

Lub możesz użyć java.text.DecimalFormati ustawić odpowiednie symbole:

DecimalFormat df = new DecimalFormat();
DecimalFormatSymbols symbols = new DecimalFormatSymbols();
symbols.setDecimalSeparator(',');
symbols.setGroupingSeparator(' ');
df.setDecimalFormatSymbols(symbols);
df.parse(p);

Tak ... jeśli nie ustawimy separatora grupowania tysięcy i użyjemy tylko formatu francuskiego, liczba w formacie hiszpańskim (1.222.222,33) zostanie przekonwertowana na „1 222 222,33”, co nie jest tym, czego chcę . Więc dziękuję!
WesternGun

1
Inną rzeczą jest, hiszpański narodowe nie znajduje się na liście „default` i nie mogę skonstruować Localez prawidłowym formacie z new Locale("es", "ES"), a następnie automatycznie analizować ciąg liczb z NumberFormat, ze ,jak po przecinku separatora i .jako separator tysięcy grupy Only. DecimalFormatDziała.
WesternGun

Dlaczego nie wszystkie kraje są tam dostępne? Czuję się dziwnie, używając francuskich ustawień regionalnych do formatowania polskich liczb ...
Wiersz

18

Jak wskazuje E-Riz, NumberFormat.parse (String) analizuje „1,23abc” jako 1,23. Aby wziąć cały wpis, możemy użyć:

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
Ta strategia jest szczegółowo wyjaśniona tutaj: ibm.com/developerworks/library/j-numberformat
Janus Varmarken

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

... jest bardzo szybki, ponieważ przeszukuje podstawową tablicę znaków na podstawie znaków po znaku. Ciąg znaków zastępujących wersje kompiluje wyrażenie RegEx do oceny.

Zasadniczo funkcja replace (char, char) jest około 10 razy szybsza, a ponieważ będziesz robić tego typu rzeczy w kodzie niskiego poziomu, warto o tym pomyśleć. Optymalizator Hot Spot tego nie rozgryzie ... Na pewno nie w moim systemie.


4

Jeśli nie znasz prawidłowego ustawienia regionalnego, a ciąg znaków może mieć separator tysięcy, może to być ostateczność:

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

Uwaga: to z przyjemnością przeanalizuje ciągi, takie jak „R 1 52.43,2” do „15243.2”.


4

Oto metoda statyczna, której używam w moim własnym kodzie:

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
Co się stanie, jeśli domyślnym ustawieniem regionalnym jest język niemiecki, gdzie przecinek oznacza miejsce dziesiętne? Możesz podać na przykład „1 000 000”, które nie zostanie przeanalizowane na niemieckie ustawienia regionalne i zostanie zastąpione przez „1 000 000”, które nie jest prawidłowym Double.
Eddie Curtis

Cześć @jimmycar, właśnie zaktualizowałem moją odpowiedź, aby używała aktualnej wersji mojej metody statycznej. Mam nadzieję, że to rozwiąże Twój problem! Pete
Pete


0

W przypadku, gdy nie znasz ustawień regionalnych otrzymanej wartości ciągu i niekoniecznie jest to to samo ustawienie regionalne, co bieżące domyślne ustawienie regionalne, możesz użyć tego:

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

}

Zwróci wartość double niezależnie od ustawień regionalnych łańcucha. I bez względu na to, ile jest przecinków lub punktów. Tak więc przekazywanie 1,000,000.54będzie działać, więc 1.000.000,54nie musisz już polegać na domyślnych ustawieniach regionalnych do analizowania ciągu. Kod nie jest tak zoptymalizowany, jak może być, więc wszelkie sugestie są mile widziane. Próbowałem przetestować większość przypadków, aby upewnić się, że rozwiązuje problem, ale nie jestem pewien, czy obejmuje wszystkie. Jeśli znajdziesz wartość zrywającą, daj mi znać.


-5

To wystarczyłoby:

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

6
Początkowe pytanie brzmiało „Czy istnieje lepszy sposób na przeanalizowanie„ 1,234 ”w celu uzyskania 1,234 niż: p = p.replaceAll („, ”,„. ”);” Jeśli uważasz, że replaceznacząco różni się od używania replaceAll, wyjaśnij dlaczego.
SuperBiasedMan
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.