安全字符串到BigDecimal的转换


120

我正在尝试从字符串中读取一些BigDecimal值。假设我有以下字符串:“ 1,000,000,000.999999999999999”,我想从中获取一个BigDecimal。怎么做呢?

首先,我不喜欢使用字符串替换(替换逗号等)的解决方案。我认为应该有一些精巧的格式化程序为我完成这项工作。

我发现了DecimalFormatter类,但是由于它通过double进行操作-损失了大量精度。

那么,我该怎么办呢?


因为给定了一些自定义格式,所以很难将其转换为BigDecimal兼容格式。
bezmax 2010年

2
“由于给定了一些自定义格式,所以很痛苦……”我不知道,它有点像将问题域分开了。首先,您从字符串中清除人类可读的内容,然后移交给知道如何正确有效地将结果转换为的内容BigDecimal
TJ Crowder 2010年

Answers:


90

看看setParseBigDecimal在DecimalFormat的。使用此设置器,parse将为您返回一个BigDecimal。


1
BigDecimal类中的构造方法不支持自定义格式。我在问题中给出的示例使用自定义格式(逗号)。您无法使用其构造函数将其解析为BigDecimal。
bezmax 2010年

24
如果您要完全更改答案,建议您在答案中提及。否则,当人们指出您的原始答案没有任何意义时,这看起来很奇怪。:-)
TJ Crowder

1
是的,没有注意到这种方法。谢谢,这似乎是最好的方法。现在要测试它,如果它可以正常工作,请接受答案。
bezmax 2010年

15
你能提供一个例子吗?
J土拨鼠

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

完整的代码来证明没有NumberFormatException抛出:

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

输出量

1000000000.999999999999999


@Steve McLeod ....不,我刚刚测试过....我的价值是1000000000.999999999999999
Buhake Sindi 2010年

10
尝试使用GERMAN语言环境和
1.000.000.000,999999999999

@#TofuBeer ....示例为1,000,000,000.999999999999999。如果我不得不做您的语言环境,我将不得不全部更换点空间....
Buhake辛迪

14
因此,这是不安全的。您不知道所有地区。
ymajoros 2014年

3
如果您只使用eg DecimalFormatSymbols.getInstance().getGroupingSeparator()而不是逗号,那是很好的,而无需担心各种语言环境。
杰森C

22

以下示例代码运行良好(需要动态获取语言环境)

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

6

代码可能更简洁,但这似乎可以解决不同区域的问题。

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

3

这是我的方法:

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

显然,如果这要在生产代码中进行,那就不是那么简单了。

我仅从字符串中删除逗号就没有问题。


如果字符串不是美国格式?在德国,则为1.000.000.000,999999999999999
史蒂夫·麦克劳德

1
这里的主要问题是,数字可能会从不同的来源获取,每个来源都有自己的格式。因此,在这种情况下,最好的方法是为每个源定义一些“数字格式”。我无法通过简单替换来做到这一点,因为一个源可以具有“ 123 456 789”格式,而另一种可以具有“ 123.456.789”格式。
bezmax 2010年

我看你是默默的重新定义的术语“一个线法” ... ;-)
彼得Török

@Steve:这是一个非常根本的区别,需要显式处理,而不是“ regexp fits all”方法。
Michael Borgwardt 2010年

2
@Max:“这里的主要问题是,数字可能会从不同的来源获取,每个来源都有自己的格式。” 其主张,而不是对抗,你的清洁移交之前输入过代码,你不用管。
TJ Crowder 2010年

3

我需要一种在不知道语言环境且不受语言环境影响的情况下将String转换为BigDecimal的解决方案。我找不到针对此问题的任何标准解决方案,因此我编写了自己的帮助程序方法。可能对其他人也有帮助:

更新:警告!此辅助方法仅适用于十进制数字,因此始终具有小数点的数字!否则,对于1000到999999(正/负)之间的数字,辅助方法可能会传递错误的结果。感谢bezmax的大力支持!

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

当然我已经测试了该方法:

@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
抱歉,由于边缘情况,我不知道这怎么用。例如,您如何知道7,333代表什么?是7333还是7和1/3(7.333)?在不同的语言环境中,该数字将被不同地解析。这里的概念证明:ideone.com/Ue9rT8
bezmax

好的输入,实际上从未遇到过这个问题。我将更新我的帖子,非常感谢!我将改进这些Locale特价商品的测试,以便知道哪里会遇到麻烦。
user3227576

1
我已经检查了针对不同区域设置和不同数字的解决方案...并且对于我们所有的作品,因为我们总是拥有带小数点的数字(我们正在从文本文件中读取Money-values)。我已经在答案中添加了警告。
user3227576

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

将从字符串中删除除数字和点以外的所有字符。

要使其具有区域设置意识,您可能要使用getDecimalSeparator()from java.text.DecimalFormatSymbols。我不懂Java,但看起来可能像这样:

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

对于非美国数字,这将带来意想不到的结果-请参阅贾斯汀答案的评论。
彼得Török

2

老话题,但也许最简单的方法是使用Apache commons NumberUtils,它具有一个createBigDecimal(String value)...方法。

我猜(希望)考虑到语言环境,否则它将毫无用处。


NumberUtils源代码中所述,createBigDecimal只是新BigDecimal(string)的包装, 其中不考虑区域设置AFAIK
Mar Bar

1

请尝试这个对我有用

BigDecimal bd ;
String value = "2000.00";

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

2
这种方式不支持为小数点和thouthands组指定自定义定界符。
bezmax 2015年

是的,但这是为了从HashMap之类的对象获取字符串BigDecimal值并将其存储到Bigdecimal值中以调用其他服务
Rajkumar Teckraft 2015年

1
是的,但这不是问题。
bezmax 2015年

也为我工作
萨西什
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.