Conversion de chaîne sécurisée en BigDecimal

120

J'essaie de lire certaines valeurs BigDecimal de la chaîne. Disons que j'ai cette chaîne: "1,000,000,000.999999999999999" et que je veux en tirer un BigDecimal. Quelle est la manière de procéder?

Tout d'abord, je n'aime pas les solutions utilisant des remplacements de chaînes (en remplaçant des virgules, etc.). Je pense qu'il devrait y avoir un formateur soigné pour faire ce travail à ma place.

J'ai trouvé une classe DecimalFormatter, mais comme elle fonctionne à travers le double, d'énormes quantités de précision sont perdues.

Alors, comment puis-je le faire?

bezmax
la source
Parce que compte tenu d'un format personnalisé, il est difficile de le faire convertir votre format en un format compatible BigDecimal.
bezmax
2
"Parce que vu un format personnalisé, c'est une douleur ..." Je ne sais pas, cela sépare en quelque sorte les domaines problématiques. Commencez par nettoyer les éléments lisibles par l'homme de la chaîne, puis passez à quelque chose qui sait comment transformer correctement et efficacement le résultat en un fichier BigDecimal.
TJ Crowder

Réponses:

90

Vérifiez setParseBigDecimaldans DecimalFormat. Avec ce setter, parseretournera un BigDecimal pour vous.

Jeroen Rosenberg
la source
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 analyser cela en BigDecimal en utilisant son constructeur.
bezmax
24
Si vous voulez changer complètement votre réponse, je vous suggère de le mentionner dans la réponse. Sinon, cela semble très étrange lorsque les gens ont expliqué pourquoi votre réponse originale n'avait aucun sens. :-)
TJ Crowder
1
Ouais, je n'ai pas remarqué cette méthode. Merci, cela semble être la meilleure façon de le faire. Je vais le tester maintenant et si cela fonctionne correctement - acceptez la réponse.
bezmax
15
Pouvez vous donner un exemple?
J Woodchuck
61
String value = "1,000,000,000.999999999999999";
BigDecimal money = new BigDecimal(value.replaceAll(",", ""));
System.out.println(money);

Code complet pour prouver que non NumberFormatExceptionest 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);
    }
}

Production

1000000000,999999999999999

Buhake Sindi
la source
@Steve McLeod .... non, je viens de le tester .... ma valeur est1000000000.999999999999999
Buhake Sindi
10
Essayez avec les paramètres régionaux allemands et 1.000.000.000,999999999999
TofuBeer
@ # TofuBeer .... l'exemple était 1.000.000.000.999999999999999. Si je devais faire vos paramètres régionaux, je devrais remplacer tous les points en espace ....
Buhake Sindi
14
Donc, ce n'est pas sûr. Vous ne connaissez pas tous les lieux.
ymajoros
3
C'est bien si vous utilisez simplement par exemple DecimalFormatSymbols.getInstance().getGroupingSeparator()au lieu d'une virgule, pas besoin de paniquer à propos des différents paramètres régionaux.
Jason C
22

L'exemple de code suivant fonctionne bien (les paramètres régionaux doivent être obtenus 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);
    }
}
Ebith
la source
6

Le code pourrait être plus propre, mais cela semble faire l'affaire pour différents paramètres régionaux.

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);
    }
}
TofuBière
la source
3

Voici comment je le ferais:

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

Évidemment, si cela se passait dans le code de production, ce ne serait pas aussi simple.

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

jjnguy
la source
Et si la chaîne n'est pas au format américain? En Allemagne, ce serait 1.000.000.000.999999999999999
Steve McLeod
1
Le problème principal ici est que les nombres peuvent être extraits de différentes sources, chacune ayant son propre format. La meilleure façon de le faire dans cette situation serait donc de définir un «format numérique» pour chacune des sources. Je ne peux pas faire cela avec des remplacements simples car une source peut avoir le format "123 456 789" et l'autre "123.456.789".
bezmax
Je vois que vous redéfinissez silencieusement le terme «méthode d'une ligne» ... ;-)
Péter Török
@Steve: c'est une différence assez fondamentale qui appelle une gestion explicite, pas une approche «regexp casse tout».
Michael Borgwardt
2
@Max: "Le principal problème ici est que les nombres pourraient être récupérés à partir de différentes sources, chacune ayant son propre format." Ce qui plaide pour , plutôt que contre, le nettoyage de l'entrée avant de passer à un code que vous ne contrôlez pas.
TJ Crowder
3

J'avais besoin d'une solution pour convertir une chaîne en BigDecimal sans connaître les paramètres régionaux et être indépendant des paramètres régionaux. Je n'ai pas trouvé de solution standard à 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'assistance ne fonctionne que pour les nombres décimaux, donc les nombres qui ont toujours un point décimal! Sinon, la méthode d'assistance pourrait donner un résultat erroné pour les nombres compris entre 1000 et 999999 (plus / moins). Merci à bezmax pour sa grande contribution!

static final String EMPTY = "";
static final String POINT = '.';
static final String COMMA = ',';
static final String POINT_AS_STRING = ".";
static final String COMMA_AS_STRING = ",";

/**
     * 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, j'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}
        };
    }
user3227576
la source
1
Désolé, mais je ne vois pas en quoi cela peut être utile à cause des cas extrêmes. Par exemple, comment savez-vous ce que 7,333représente? Est-ce 7333 ou 7 et 1/3 (7,333)? Dans différents paramètres régionaux, ce nombre serait analysé différemment. Voici la preuve de concept: ideone.com/Ue9rT8
bezmax
Bonne entrée, jamais eu ce problème dans la pratique. Je mettrai à jour mon message, merci beaucoup! Et j'améliorerai les tests pour ces promotions locales juste pour savoir où seront les problèmes.
user3227576
1
J'ai vérifié la solution pour différents paramètres régionaux et différents nombres ... et pour nous tous fonctionne, car nous avons toujours des nombres avec un point décimal (nous lisons des valeurs Money à partir d'un fichier texte). J'ai ajouté un avertissement à la réponse.
user3227576
2
resultString = subjectString.replaceAll("[^.\\d]", "");

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

Pour le rendre compatible avec les paramètres régionaux, vous pouvez utiliser getDecimalSeparator()from java.text.DecimalFormatSymbols. Je ne connais pas Java, mais cela pourrait ressembler à ceci:

sep = getDecimalSeparator()
resultString = subjectString.replaceAll("[^"+sep+"\\d]", "");
Tim Pietzcker
la source
Ce qui donnera des résultats inattendus pour les numéros non américains - voir les commentaires sur la réponse de Justin.
Péter Török
2

Ancien sujet mais peut-être le plus simple est d'utiliser Apache commons NumberUtils qui a une méthode createBigDecimal (String value) ....

J'imagine (j'espère) que cela prend en compte les paramètres régionaux sinon ce serait plutôt inutile.

Lawrence
la source
createBigDecimal est uniquement un wrapper pour le nouveau BigDecimal (chaîne), comme indiqué dans le code source NumberUtils qui ne considère pas de paramètres régionaux AFAIK
Mar Bar
1

S'il vous plaît essayez cela, cela fonctionne pour moi

BigDecimal bd ;
String value = "2000.00";

bd = new BigDecimal(value);
BigDecimal currency = bd;
Rajkumar Teckraft
la source
2
Cette méthode ne prend pas en charge la spécification de délimiteurs personnalisés pour les groupes de points décimaux et de mains.
bezmax
Oui, mais c'est pour obtenir la valeur BigDecimal de la chaîne à partir d'un objet comme HashMap et la stocker dans une valeur Bigdecimal pour appeler les autres services
Rajkumar Teckraft
1
Oui, mais ce n'était pas la question.
bezmax
Fonctionne aussi pour moi
Sathesh