Meilleur moyen d'analyserDouble avec une virgule comme séparateur décimal?

148

Ce qui suit entraîne un Exception:

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

Existe-t-il un meilleur moyen d'analyser "1,234"pour obtenir 1.234que p = p.replaceAll(",",".");:?

Koerr
la source
17
D'après mon expérience, replaceAll (), comme vous l'avez suggéré, est le meilleur moyen de le faire. Cela ne dépend pas de la locale actuelle, c'est simple et cela fonctionne.
Joonas Pulakka
1
@Marco Altieri: replaceAll(",",".")remplace toutes les virgules par des points. S'il n'y a pas de virgule, cela ne fait rien. Double.valueOf()fonctionne (uniquement) avec les chaînes qui utilisent le point comme séparateur décimal. Rien ici n'est affecté par les paramètres régionaux par défaut actuels. docs.oracle.com/javase/8/docs/api/java/lang/…
Joonas Pulakka
4
Le seul problème avec replaceAll(",",".")est que cela ne fonctionnera que s'il y a une seule virgule: c'est-à-dire: 1 234 567 lancera java.lang.NumberFormatException: multiple points. Une expression régulière avec une anticipation positive suffira p.replaceAll(",(?=[0-9]+,)", "").replaceAll(",", ".")Plus à: regular-expressions.info/lookaround.html
artemisian
2
Il n'y a pas de problème. Le NumberFormatException est bon. Comment savoir quelle virgule est la bonne? Le format est incorrect et tout ce que vous pouvez faire est d'afficher un message plus lisible que l'exception à l'utilisateur.
L'incroyable
2
@TheincredibleJan Non, le format n'est pas faux. Certains paramètres régionaux utilisent la virgule comme séparateur de milliers, vous pouvez donc en avoir plusieurs dans un nombre et c'est techniquement toujours une entrée valide.
Vratislav Jindra

Réponses:

206

Utilisez java.text.NumberFormat :

NumberFormat format = NumberFormat.getInstance(Locale.FRANCE);
Number number = format.parse("1,234");
double d = number.doubleValue();
dogbane
la source
11
Cela ne fonctionne que si les paramètres régionaux par défaut actuels utilisent une virgule comme séparateur décimal.
Joonas Pulakka
6
Pour gâcher encore plus les choses, certaines locales utilisent la virgule comme séparateur de milliers , auquel cas "1,234" analysera jusqu'à 1234.0 au lieu de renvoyer une erreur.
Joonas Pulakka
17
Le problème avec NumberFormat est qu'il ignorera silencieusement les caractères invalides. Donc, si vous essayez d'analyser "1,23abc", il retournera volontiers 1.23 sans vous indiquer que la chaîne transmise contenait des caractères non analysables. Dans certaines situations, cela peut être souhaitable, mais je ne pense pas que ce soit généralement le comportement souhaité.
E-Riz
7
pour la TURQUIE, vous devez utiliser NumberFormat.getInstance (new Locale (tr_TR))
Günay Gültekin
2
pour qui utilise quel séparateur voir en.wikipedia.org/w
...
67

Vous pouvez utiliser ceci (la langue française a ,pour séparateur décimal)

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

Ou vous pouvez utiliser java.text.DecimalFormatet définir les symboles appropriés:

DecimalFormat df = new DecimalFormat();
DecimalFormatSymbols symbols = new DecimalFormatSymbols();
symbols.setDecimalSeparator(',');
symbols.setGroupingSeparator(' ');
df.setDecimalFormatSymbols(symbols);
df.parse(p);
Bozho
la source
Oui ... si nous ne définissons pas le séparateur des milliers et utilisons simplement le format français, un nombre au format espagnol (1.222.222,33) sera converti en "1 222 222,33", ce qui n'est pas ce que je veux . Donc merci!
WesternGun
1
Une autre chose est que les paramètres régionaux espagnols ne sont pas répertoriés comme "par défaut" et que je ne peux pas créer un Localeformat correct avec new Locale("es", "ES"), puis analyser automatiquement la chaîne numérique avec NumberFormat, avec ,comme séparateur décimal et .comme séparateur de milliers de groupes. Fonctionne uniquement DecimalFormat.
WesternGun
Pourquoi tous les pays n'y sont-ils pas disponibles? Je me sens bizarre d'utiliser les paramètres régionaux français pour formater les nombres polonais ...
Ligne
18

Comme le souligne E-Riz, NumberFormat.parse (String) analyse "1,23abc" comme étant 1.23. Pour prendre toute l'entrée, nous pouvons utiliser:

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
la source
2
Cette stratégie est expliquée en détail ici: ibm.com/developerworks/library/j-numberformat
Janus Varmarken
7
Double.parseDouble(p.replace(',','.'))

... est très rapide car il recherche le tableau de caractères sous-jacent caractère par caractère. La chaîne remplace les versions compilent un RegEx à évaluer.

En gros, replace (char, char) est environ 10 fois plus rapide et puisque vous ferez ce genre de choses dans un code de bas niveau, il est logique d'y penser. L'optimiseur Hot Spot ne le comprendra pas ... Certainement pas sur mon système.

FuséeBanane
la source
4

Si vous ne connaissez pas les paramètres régionaux corrects et que la chaîne peut avoir un séparateur de milliers, cela peut être un dernier recours:

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

Attention: cela analysera volontiers des chaînes comme "R 1 52.43,2" à "15243.2".

user3506443
la source
4

C'est la méthode statique que j'utilise dans mon propre code:

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
la source
5
Que faire si le paramètre régional par défaut est quelque chose comme l'allemand, où une virgule indique une décimale? Vous pouvez passer, par exemple, "1,000,000" qui ne serait pas analysé dans les paramètres régionaux allemands et serait alors remplacé par "1.000.000" qui n'est pas un double valide.
Eddie Curtis
Salut @jimmycar, je viens de mettre à jour ma réponse pour utiliser la version actuelle de ma méthode statique. J'espère que cela résoudra votre problème! Pete
Pete
1

Vous devez bien sûr utiliser les paramètres régionaux appropriés. Cette question aidera.

kgiannakakis
la source
0

Dans le cas où vous ne connaissez pas les paramètres régionaux de la valeur de chaîne reçue et qu'il ne s'agit pas nécessairement des mêmes paramètres régionaux que les paramètres régionaux par défaut actuels, vous pouvez utiliser ceci:

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

}

Il renverra un double quelle que soit la locale de la chaîne. Et peu importe le nombre de virgules ou de points. Le passage 1,000,000.54fonctionnera donc, 1.000.000,54vous n'aurez plus à vous fier aux paramètres régionaux par défaut pour analyser la chaîne. Le code n'est pas aussi optimisé qu'il peut l'être, toutes les suggestions sont donc les bienvenues. J'ai essayé de tester la plupart des cas pour m'assurer que cela résout le problème, mais je ne suis pas sûr que cela couvre tous. Si vous trouvez une valeur de rupture, faites-le moi savoir.

Amro Elaswar
la source
-5

Cela ferait le travail:

Double.parseDouble(p.replace(',','.')); 
Helge
la source
6
La question initiale disait "Y a-t-il une meilleure façon d'analyser" 1,234 "pour obtenir 1,234 que: p = p.replaceAll (", ",". ");" , si vous pensez que cela replacediffère considérablement de l'utilisation replaceAll, veuillez expliquer pourquoi.
SuperBiasMan