Safe String to BigDecimal conversion

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?

394021 次浏览

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

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.

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

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

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

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

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.

Please try this its working for me

BigDecimal bd ;
String value = "2000.00";


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

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