2010-09-20 6 views
95

J'essaie de lire certaines valeurs BigDecimal de la chaîne. Disons que j'ai cette chaîne: "1,000,000,000.999999999999999" et je veux en extraire un BigDecimal. Quelle est la façon de le faire? Tout d'abord, je n'aime pas les solutions utilisant des remplacements de chaînes (remplacement des virgules etc.). Je pense qu'il devrait y avoir un formateur soigné pour faire ce travail pour moi.Conversion Safe String en BigDecimal

J'ai trouvé une classe DecimalFormatter, mais comme elle fonctionne par le biais de doubles, une grande quantité de précision est perdue.

Alors, comment puis-je le faire?

+2

Pourquoi pas String.replaceAll? – TofuBeer

+0

Parce que donné un format personnalisé, il est difficile de le convertir en format compatible BigDecimal. – bezmax

+2

* "Parce que donné un certain format personnalisé c'est une douleur ..." * Je ne sais pas, il sépare les domaines de problème. Vous devez d'abord nettoyer les éléments lisibles par l'utilisateur, puis passer à quelque chose qui sait comment transformer correctement le résultat en un BigDecimal. –

Répondre

79

Extrayez le setParseBigDecimal dans DecimalFormat. Avec ce setter, parse retournera un BigDecimal pour vous.

+1

Le constructeur de la classe BigDecimal ne prend pas en charge les formats personnalisés. L'exemple que j'ai donné dans la question utilise le format personnalisé (virgules). Vous ne pouvez pas l'analyser en BigDecimal en utilisant son constructeur. – bezmax

+23

Si vous allez ** complètement ** changer votre réponse, je suggère de le mentionner dans la réponse. Sinon, il semble très étrange lorsque les gens ont souligné pourquoi votre réponse originale n'a pas de sens. :-) –

+1

Ouais, n'a pas remarqué cette méthode. Merci, cela semble être la meilleure façon de le faire. Va le tester maintenant et si ça fonctionne correctement - accepte la réponse. – bezmax

3

Voici comment je le ferais:

public String cleanDecimalString(String input, boolean americanFormat) { 
    if (americanFormat) 
     return input.replaceAll(",", ""); 
    else 
     return input.replaceAll(".", ""); 
} 

Il est évident que, si cela se passait dans le code de production, il ne serait pas aussi simple que cela.

Je ne vois aucun problème à simplement supprimer les virgules de la chaîne.

+0

Et si la chaîne n'est pas au format américain? En Allemagne, ce serait 1.000.000.000,999999999999999 –

+0

@Steve, bon appel – jjnguy

+2

Donc, vous utilisez les paramètres régionaux pour le nettoyer des choses correctes. –

45
String value = "1,000,000,000.999999999999999"; 
BigDecimal money = new BigDecimal(value.replaceAll(",", "")); 
System.out.println(money); 

code complet pour prouver qu'aucun NumberFormatException est jeté:

import java.math.BigDecimal; 

public class Tester { 
    public static void main(String[] args) { 
     // TODO Auto-generated method stub 
     String value = "1,000,000,000.999999999999999"; 
     BigDecimal money = new BigDecimal(value.replaceAll(",", "")); 
     System.out.println(money); 
    } 
} 

Sortie

1000000000,999999999999999

+0

@Steve McLeod .... non, je viens de le tester .... ma valeur est '1000000000.999999999999999' –

+7

Essayez avec les paramètres régionaux allemands, et 1.000.000.000,999999999999 – TofuBeer

+0

@ # TofuBeer .... l'exemple était 1.000.000.000.999999999999999.Si je devais faire votre locale, je devrais remplacer tous les points à l'espace .... –

2
resultString = subjectString.replaceAll("[^.\\d]", ""); 

va supprimer tous les caractères sauf les chiffres et le point de votre chaîne.

Pour le rendre sensible aux paramètres régionaux, vous pouvez utiliser getDecimalSeparator() de java.text.DecimalFormatSymbols. Je ne sais pas Java, mais il pourrait ressembler à ceci:

sep = getDecimalSeparator() 
resultString = subjectString.replaceAll("[^"+sep+"\\d]", ""); 
+0

Ce qui donnera des résultats inattendus pour les numéros non-américains - voir les commentaires à la réponse de Justin. –

+0

Je sais, était juste en train d'éditer ma réponse lorsque vous avez commenté :) –

5

Le code pourrait être plus propre, mais cela semble faire l'affaire pour différents endroits.

import java.math.BigDecimal; 
import java.text.DecimalFormatSymbols; 
import java.util.Locale; 


public class Main 
{ 
    public static void main(String[] args) 
    { 
     final BigDecimal numberA; 
     final BigDecimal numberB; 

     numberA = stringToBigDecimal("1,000,000,000.999999999999999", Locale.CANADA); 
     numberB = stringToBigDecimal("1.000.000.000,999999999999999", Locale.GERMANY); 
     System.out.println(numberA); 
     System.out.println(numberB); 
    } 

    private static BigDecimal stringToBigDecimal(final String formattedString, 
               final Locale locale) 
    { 
     final DecimalFormatSymbols symbols; 
     final char     groupSeparatorChar; 
     final String    groupSeparator; 
     final char     decimalSeparatorChar; 
     final String    decimalSeparator; 
     String      fixedString; 
     final BigDecimal   number; 

     symbols    = new DecimalFormatSymbols(locale); 
     groupSeparatorChar = symbols.getGroupingSeparator(); 
     decimalSeparatorChar = symbols.getDecimalSeparator(); 

     if(groupSeparatorChar == '.') 
     { 
      groupSeparator = "\\" + groupSeparatorChar; 
     } 
     else 
     { 
      groupSeparator = Character.toString(groupSeparatorChar); 
     } 

     if(decimalSeparatorChar == '.') 
     { 
      decimalSeparator = "\\" + decimalSeparatorChar; 
     } 
     else 
     { 
      decimalSeparator = Character.toString(decimalSeparatorChar); 
     } 

     fixedString = formattedString.replaceAll(groupSeparator , ""); 
     fixedString = fixedString.replaceAll(decimalSeparator , "."); 
     number  = new BigDecimal(fixedString); 

     return (number); 
    } 
} 
18

Le code exemple suivant fonctionne bien (locale doivent être obtenues dynamiquement)

import java.math.BigDecimal; 
import java.text.NumberFormat; 
import java.text.DecimalFormat; 
import java.text.ParsePosition; 
import java.util.Locale; 

class TestBigDecimal { 
    public static void main(String[] args) { 

     String str = "0,00"; 
     Locale in_ID = new Locale("in","ID"); 
     //Locale in_ID = new Locale("en","US"); 

     DecimalFormat nf = (DecimalFormat)NumberFormat.getInstance(in_ID); 
     nf.setParseBigDecimal(true); 

     BigDecimal bd = (BigDecimal)nf.parse(str, new ParsePosition(0)); 

     System.out.println("bd value : " + bd); 
    } 
} 
0

Vieux sujet, mais peut-être le plus facile est d'utiliser Apache Commons NumberUtils qui a une méthode createBigDecimal (valeur String). ...

Je suppose (j'espère) qu'il faut tenir compte des locales ou bien cela serait plutôt inutile.

+0

createBigDecimal est seulement un wrapper pour new BigDecimal (chaîne), comme indiqué dans le [NumberUtils Source Code] (https://commons.apache.org/proper/commons -lang/javadocs/api-3.1/src-html/org/apache/commons/lang3/math/NumberUtils.html # line.709) qui ne tient pas compte de Locale AFAIK –

0

S'il vous plaît essayer ce son travail pour moi

BigDecimal bd ; 
String value = "2000.00"; 

bd = new BigDecimal(value); 
BigDecimal currency = bd; 
+1

De cette façon, il n'est pas possible de spécifier des délimiteurs personnalisés pour la virgule décimale et les poignées groupes. – bezmax

+0

Oui, mais c'est pour obtenir la valeur BigDecimal chaîne de l'objet comme HashMap et le stockage en valeur Bigdecimal pour appeler les autres services –

+1

Oui, mais ce n'était pas la question. – bezmax

2

je besoin d'une solution pour convertir une chaîne en BigDecimal sans connaître la région et d'être indépendant des paramètres régionaux. Je n'ai pas trouvé de solution standard pour ce problème, j'ai donc écrit ma propre méthode d'aide. Peut être que cela aide aussi quelqu'un d'autre:

Mise à jour: Attention! Cette méthode d'aide fonctionne uniquement pour les nombres décimaux, donc les nombres qui ont toujours un point décimal! Sinon, la méthode d'assistance pourrait donner un mauvais résultat pour les nombres entre 1000 et 999999 (plus/moins). Merci à bezmax pour sa grande contribution!

/** 
    * Converts a String to a BigDecimal. 
    *  if there is more than 1 '.', the points are interpreted as thousand-separator and will be removed for conversion 
    *  if there is more than 1 ',', the commas are interpreted as thousand-separator and will be removed for conversion 
    * the last '.' or ',' will be interpreted as the separator for the decimal places 
    * () or - in front or in the end will be interpreted as negative number 
    * 
    * @param value 
    * @return The BigDecimal expression of the given string 
    */ 
    public static BigDecimal toBigDecimal(final String value) { 
     if (value != null){ 
      boolean negativeNumber = false; 

      if (value.containts"(") && value.contains(")") 
       negativeNumber = true; 
      if (value.endsWith("-") || value.startsWith("-")) 
       negativeNumber = true; 

      String parsedValue = value.replaceAll("[^0-9\\,\\.], EMPTY); 

      if (negativeNumber) 
       parsedValue = "-" + parsedValue; 

      int lastPointPosition = parsedValue.lastIndexOf(POINT); 
      int lastCommaPosition = parsedValue.lastIndexOf(COMMA); 

      //handle '1423' case, just a simple number 
      if (lastPointPosition == -1 && lastCommaPosition == -1) 
       return new BigDecimal(parsedValue); 
      //handle '45.3' and '4.550.000' case, only points are in the given String 
      if (lastPointPosition > -1 && lastCommaPosition == -1){ 
       int firstPointPosition = parsedValue.indexOf(POINT); 
       if (firstPointPosition != lastPointPosition) 
        return new BigDecimal(parsedValue.replace(POINT_AS_STRING, EMPTY)); 
       else 
        return new BigDecimal(parsedValue); 
      } 
      //handle '45,3' and '4,550,000' case, only commas are in the given String 
      if (lastPointPosition == -1 && lastCommaPosition > -1){ 
       int firstCommaPosition = parsedValue.indexOf(COMMA); 
       if (firstCommaPosition != lastCommaPosition) 
        return new BigDecimal(parsedValue.replace(COMMA_AS_STRING, EMPTY)); 
       else 
        return new BigDecimal(parsedValue.replace(COMMA, POINT)); 
      } 
      //handle '2.345,04' case, points are in front of commas 
      if (lastPointPosition < lastCommaPosition){ 
       parsedValue = parsedValue.replace(POINT_AS_STRING, EMPTY); 
       return new BigDecimal(parsedValue.replace(COMMA, POINT)); 
      } 
      //handle '2,345.04' case, commas are in front of points 
      if (lastCommaPosition < lastPointPosition){ 
       parsedValue = parsedValue.replace(COMMA_AS_STRING, EMPTY); 
       return new BigDecimal(parsedValue); 
      } 
      throw new NumberFormatException("Unexpected number format. Cannot convert '" + value + "' to BigDecimal."); 
     } 
     return null; 
    } 

Bien sûr, je l'ai testé la méthode:

@Test(dataProvider = "testBigDecimals") 
    public void toBigDecimal_defaultLocaleTest(String stringValue, BigDecimal bigDecimalValue){ 
     BigDecimal convertedBigDecimal = DecimalHelper.toBigDecimal(stringValue); 
     Assert.assertEquals(convertedBigDecimal, bigDecimalValue); 
    } 
    @DataProvider(name = "testBigDecimals") 
    public static Object[][] bigDecimalConvertionTestValues() { 
     return new Object[][] { 
       {"5", new BigDecimal(5)}, 
       {"5,3", new BigDecimal("5.3")}, 
       {"5.3", new BigDecimal("5.3")}, 
       {"5.000,3", new BigDecimal("5000.3")}, 
       {"5.000.000,3", new BigDecimal("5000000.3")}, 
       {"5.000.000", new BigDecimal("5000000")}, 
       {"5,000.3", new BigDecimal("5000.3")}, 
       {"5,000,000.3", new BigDecimal("5000000.3")}, 
       {"5,000,000", new BigDecimal("5000000")}, 
       {"+5", new BigDecimal("5")}, 
       {"+5,3", new BigDecimal("5.3")}, 
       {"+5.3", new BigDecimal("5.3")}, 
       {"+5.000,3", new BigDecimal("5000.3")}, 
       {"+5.000.000,3", new BigDecimal("5000000.3")}, 
       {"+5.000.000", new BigDecimal("5000000")}, 
       {"+5,000.3", new BigDecimal("5000.3")}, 
       {"+5,000,000.3", new BigDecimal("5000000.3")}, 
       {"+5,000,000", new BigDecimal("5000000")}, 
       {"-5", new BigDecimal("-5")}, 
       {"-5,3", new BigDecimal("-5.3")}, 
       {"-5.3", new BigDecimal("-5.3")}, 
       {"-5.000,3", new BigDecimal("-5000.3")}, 
       {"-5.000.000,3", new BigDecimal("-5000000.3")}, 
       {"-5.000.000", new BigDecimal("-5000000")}, 
       {"-5,000.3", new BigDecimal("-5000.3")}, 
       {"-5,000,000.3", new BigDecimal("-5000000.3")}, 
       {"-5,000,000", new BigDecimal("-5000000")}, 
       {null, null} 
     }; 
    } 
+1

Désolé, mais je ne vois pas comment cela peut être utile en raison de bord-cas. Par exemple, comment savez-vous ce que «7 333» représente? Est-ce 7333 ou 7 et 1/3 (7.333)? Dans différents endroits ce nombre serait analysé différemment. Voici une preuve de concept: http://ideone.com/Ue9rT8 – bezmax

+0

Bonne entrée, n'a jamais eu ce problème en pratique. Je vais mettre à jour mon post, merci beaucoup! Et j'améliorerai le test pour ces spéciaux de Locale juste pour savoir où recevra des ennuis. – user3227576

+1

J'ai vérifié la solution pour différents Locales et différents nombres ... et pour nous tous fonctionne, parce que nous avons toujours des nombres avec un point décimal (nous lisons des valeurs d'argent d'un fichier texte). J'ai ajouté un avertissement à la réponse. – user3227576