130

I'm trying to read some BigDecimal values from the string. Let's say I have this String: "1,000,000,000.999999999999999" and I want to get a BigDecimal out of it. What is the way to do it?

First of all, I don't like the solutions using string replaces (replacing commas etc.). I think there should be some neat formatter to do that job for me.

I've found a DecimalFormatter class, however as it operates through double - huge amounts of precision are lost.

So, how can I do it?

Kiquenet
  • 14,494
  • 35
  • 148
  • 243
bezmax
  • 25,562
  • 10
  • 53
  • 84
  • Because given some custom format it is a pain to make it convert your format into BigDecimal-compatible format. – bezmax Sep 20 '10 at 14:53
  • 2
    *"Because given some custom format it is a pain..."* I dunno, it kind of separates problem domains. First you clean the human-readable stuff out of the string, then hand off to something that knows how to correctly and efficiently turn the result into a `BigDecimal`. – T.J. Crowder Sep 20 '10 at 14:56

9 Answers9

91

Check out setParseBigDecimal in DecimalFormat. With this setter, parse will return a BigDecimal for you.

Bax
  • 4,260
  • 5
  • 43
  • 65
Jeroen Rosenberg
  • 4,552
  • 3
  • 27
  • 39
  • 1
    Constructor in the BigDecimal class does not support custom formats. The example I gave in the question uses the custom format (commas). You can't parse that to BigDecimal using it's constructor. – bezmax Sep 20 '10 at 14:56
  • 28
    If you're going to **completely** change your answer, I'd suggest mentioning it in the answer. Else it looks very odd when people have pointed out why your original answer didn't make any sense. :-) – T.J. Crowder Sep 20 '10 at 15:03
  • 1
    Yeah, did not notice that method. Thanks, that seems to be the best way to do it. Gonna test it now and if it works properly - accept the answer. – bezmax Sep 20 '10 at 15:03
  • 19
    can you provide an example? – Woodchuck May 06 '16 at 22:53
  • downvote, no MRE – FARS Jul 24 '23 at 18:43
67
String value = "1,000,000,000.999999999999999";
BigDecimal money = new BigDecimal(value.replaceAll(",", ""));
System.out.println(money);

Full code to prove that no NumberFormatException is thrown:

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

Output

1000000000.999999999999999

0x5C91
  • 3,360
  • 3
  • 31
  • 46
Buhake Sindi
  • 87,898
  • 29
  • 167
  • 228
  • @Steve McLeod....nope, I just tested it.... my value is `1000000000.999999999999999` – Buhake Sindi Sep 20 '10 at 15:09
  • 11
    Try with GERMAN locale, and 1.000.000.000,999999999999 – TofuBeer Sep 20 '10 at 15:16
  • @#TofuBeer....the example was 1,000,000,000.999999999999999. If I had to do your locale, I would have to replace all dots to space.... – Buhake Sindi Sep 20 '10 at 15:34
  • 16
    So, it's not safe. You don't know all locales. – ymajoros Jan 23 '14 at 12:22
  • 4
    It's fine if you just use e.g. [`DecimalFormatSymbols.getInstance().getGroupingSeparator()`](https://docs.oracle.com/javase/7/docs/api/java/text/DecimalFormatSymbols.html#getGroupingSeparator()) instead of a comma, no need to freak out about varying locales. – Jason C Mar 08 '16 at 15:35
  • Which tool you use for test ? `Auto-generated method stub` – Kiquenet Jun 01 '17 at 10:49
  • The `Auto-generated method stub` is what Eclipse IDE generates when you select it to create a main method or override a method. Normally, I write a JUnit test but for this exercise, a simple application sufficed. – Buhake Sindi Jun 01 '17 at 10:52
26

The following sample code works well (locale need to be obtained dynamically)

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
  • 261
  • 3
  • 2
6

The code could be cleaner, but this seems to do the trick for different locales.

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);
    }
}
TofuBeer
  • 60,850
  • 18
  • 118
  • 163
4

I needed a solution to convert a String to a BigDecimal without knowing the locale and being locale-independent. I couldn't find any standard solution for this problem so i wrote my own helper method. May be it helps anybody else too:

Update: Warning! This helper method works only for decimal numbers, so numbers which always have a decimal point! Otherwise the helper method could deliver a wrong result for numbers between 1000 and 999999 (plus/minus). Thanks to bezmax for his great input!

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

Of course i've tested the method:

@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}
        };
    }
Piro
  • 1,367
  • 2
  • 19
  • 40
user3227576
  • 554
  • 8
  • 22
  • 1
    Sorry, but I don't see how this can be useful because of edge-cases. For example, how do you know what does `7,333` represent? Is it 7333 or 7 and 1/3 (7.333) ? In different locales that number would be parsed differently. Here's proof of concept: http://ideone.com/Ue9rT8 – bezmax Jan 17 '17 at 17:48
  • Good input, never had this problem in practice. I will update my post, thank you very much! And i will improve the testing for these Locale specials just to know where will receive troubles. – user3227576 Jan 18 '17 at 09:38
  • 1
    I have checked the solution for different Locales and different numbers... and for us all works, because we always have numbers with a decimal point (we are reading Money-values from a textfile). I have added a warning to the answer. – user3227576 Jan 18 '17 at 12:06
3

Here is how I would do it:

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

Obviously, if this were going in production code, it wouldn't be that simple.

I see no issue with simply removing the commas from the String.

jjnguy
  • 136,852
  • 53
  • 295
  • 323
  • And if the string is not in American format? In Germany, it would be 1.000.000.000,999999999999999 – Steve McLeod Sep 20 '10 at 14:57
  • 1
    Main problem here is that the numbers could be fetched from different sources each having it's own format. So the best way to do it in this situation would be defining some "number format" for each of the sources. I can't do that with simple replacings as one source can have "123 456 789" format and the other "123.456.789" one. – bezmax Sep 20 '10 at 14:59
  • I see you are silently redefining the term "one line method"... ;-) – Péter Török Sep 20 '10 at 15:01
  • @Steve: that's a pretty fundamental difference that calls for explicit handling, not a "regexp fits all" approach. – Michael Borgwardt Sep 20 '10 at 15:02
  • @Peter, yeah. Oversight on my part. That sentence has been removed though. – jjnguy Sep 20 '10 at 15:02
  • 2
    @Max: *"Main problem here is that the numbers could be fetched from different sources each having it's own format."* Which argues **for**, rather than against, your cleaning the input before handing off to code you don't control. – T.J. Crowder Sep 20 '10 at 15:04
  • If you're going to assume that you can have inputs from different locales, then they are going to have to be stamped with a locale somehow. I suppose you could say that if you see "1,234,567.890" you know it's American style because you can't have more than one decimal point. Similarly "1.234.567,890" must be German style. But what if you see "123,456"? There's no way to tell without someone telling you. – Jay Sep 20 '10 at 16:28
  • @jay, that is correct. You cannot know the locale for sure without someone telling you first. – jjnguy Sep 20 '10 at 16:37
3
resultString = subjectString.replaceAll("[^.\\d]", "");

will remove all characters except digits and the dot from your string.

To make it locale-aware, you might want to use getDecimalSeparator() from java.text.DecimalFormatSymbols. I don't know Java, but it might look like this:

sep = getDecimalSeparator()
resultString = subjectString.replaceAll("[^"+sep+"\\d]", "");
Tim Pietzcker
  • 328,213
  • 58
  • 503
  • 561
2

Old topic but maybe the easiest is to use Apache commons NumberUtils which has a method createBigDecimal (String value)....

I guess (hope) it takes locales into account or else it would be rather useless.

Lawrence
  • 1,035
  • 13
  • 8
  • createBigDecimal is only a wrapper for new BigDecimal(string), as stated in the [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) which considers no Locale AFAIK – Mar Bar Nov 04 '15 at 20:09
0

Please try this its working for me

BigDecimal bd ;
String value = "2000.00";

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