【问题标题】:Safe String to BigDecimal conversion安全字符串到 BigDecimal 的转换
【发布时间】:2010-09-20 14:49:26
【问题描述】:

我正在尝试从字符串中读取一些 BigDecimal 值。假设我有这个字符串:“1,000,000,000.999999999999999”,我想从中得到一个 BigDecimal。有什么办法呢?

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

我找到了一个 DecimalFormatter 类,但是当它通过 double 运行时,会丢失大量的精度。

那么,我该怎么做呢?

【问题讨论】:

  • 因为给定了一些自定义格式,将您的格式转换为兼容 BigDecimal 的格式是很痛苦的。
  • “因为给定了一些自定义格式,这很痛苦……” 我不知道,它有点区分问题域。首先你从字符串中清除人类可读的东西,然后交给知道如何正确有效地将结果转换为BigDecimal的东西。

标签: java parsing bigdecimal


【解决方案1】:

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

【讨论】:

  • BigDecimal 类中的构造函数不支持自定义格式。我在问题中给出的示例使用自定义格式(逗号)。您无法使用它的构造函数将其解析为 BigDecimal。
  • 如果您要完全更改您的答案,我建议您在答案中提及。否则,当人们指出为什么您的原始答案没有任何意义时,这看起来很奇怪。 :-)
  • 是的,没有注意到那个方法。谢谢,这似乎是最好的方法。现在要测试它,如果它工作正常 - 接受答案。
  • 你能举个例子吗?
【解决方案2】:
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
  • 尝试使用德语语言环境和 1.000.000.000,999999999999
  • @#TofuBeer....示例是 1,000,000,000.999999999999999。如果我必须做你的语言环境,我将不得不将所有点替换为空格......
  • 所以,这不安全。你并不知道所有的语言环境。
  • 如果你只使用 e.g. DecimalFormatSymbols.getInstance().getGroupingSeparator() 而不是逗号,无需担心不同的语言环境。
【解决方案3】:

以下示例代码运行良好(需要动态获取locale)

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

【讨论】:

    【解决方案4】:

    代码可能更简洁,但这似乎对不同的语言环境有用。

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

    【讨论】:

      【解决方案5】:

      我会这样做:

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

      显然,如果这是在生产代码中,它不会那么简单。

      我认为简单地从字符串中删除逗号没有问题。

      【讨论】:

      • 如果字符串不是美式格式?在德国,它将是 1.000.000.000,999999999999999
      • 这里的主要问题是数字可以从不同的来源获取,每个来源都有自己的格式。因此,在这种情况下,最好的方法是为每个来源定义一些“数字格式”。我不能通过简单的替换来做到这一点,因为一个来源可以有“123 456 789”格式,而另一个来源可以有“123.456.789”格式。
      • 我看到你在默默地重新定义“单行方法”这个词...... ;-)
      • @Steve:这是一个非常根本的区别,需要显式处理,而不是“适合所有人的正则表达式”方法。
      • @Max: “这里的主要问题是可以从不同的来源获取这些数字,每个来源都有自己的格式。” 这认为 对于,而不是反对,您在移交给您无法控制的代码之前清理输入。
      【解决方案6】:
      resultString = subjectString.replaceAll("[^.\\d]", "");
      

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

      要使其具有区域设置感知能力,您可能需要使用来自java.text.DecimalFormatSymbolsgetDecimalSeparator()。我不懂Java,但它可能看起来像这样:

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

      【讨论】:

      • 这会给非美国数字带来意想不到的结果 - 请参阅贾斯汀答案的 cmets。
      【解决方案7】:

      我需要一种解决方案来将字符串转换为 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}
              };
          }
      

      【讨论】:

      • 对不起,但由于边缘情况,我不明白这有什么用处。例如,你怎么知道7,333 代表什么?是 7333 还是 7 和 1/3 (7.333) ?在不同的语言环境中,该数字的解析方式会有所不同。这是概念证明:ideone.com/Ue9rT8
      • 很好的输入,在实践中从未遇到过这个问题。我会更新我的帖子,非常感谢!我会改进对这些 Locale specials 的测试,只是为了知道哪里会遇到麻烦。
      • 我已经检查了不同区域设置和不同数字的解决方案......并且对我们来说都是有效的,因为我们总是有带小数点的数字(我们正在从文本文件中读取货币值)。我在答案中添加了警告。
      【解决方案8】:

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

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

      【讨论】:

      • createBigDecimal 只是新 BigDecimal(string) 的包装器,如 NumberUtils Source Code 中所述,它不考虑区域设置 AFAIK
      【解决方案9】:

      请试试这个对我有用

      BigDecimal bd ;
      String value = "2000.00";
      
      bd = new BigDecimal(value);
      BigDecimal currency = bd;
      

      【讨论】:

      • 这种方式不支持为小数点和千位组指定自定义分隔符。
      • 是的,但这是为了从像HashMap这样的对象中获取字符串BigDecimal值并存储到Bigdecimal值中以调用其他服务
      • 是的,但这不是问题。
      • 也适合我
      猜你喜欢
      • 1970-01-01
      • 2018-11-29
      • 2022-01-23
      • 1970-01-01
      • 2010-12-06
      • 2014-05-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多