解析带有小数和分组分隔符的货币字符串,或者如果 Pattern 不匹配则抛出

Posted

技术标签:

【中文标题】解析带有小数和分组分隔符的货币字符串,或者如果 Pattern 不匹配则抛出【英文标题】:Parse a money String with decimal and grouping separator or throw if Pattern does not match 【发布时间】:2021-07-25 19:55:16 【问题描述】:

我想知道是否有一个类或库可以让我:

定义小数分隔符 定义分组分隔符 定义分组大小 将字符串解析为 BigDecimal 或 Double 如果小数分隔符、分组分隔符或分组大小不匹配,则抛出异常

这背后的原因是我需要从文件中解析货币值 并且这些格式可能会改变。如果确实发生了变化,我需要避免解析错误的数字。

例子:

在文件 foo.csv 中,值的格式是用点作为分组分隔符,用逗号作为小数分隔符,例如1.234,54。 在较新的文件 bar.csv 中,相同的数字被格式化,没有分组分隔符和点作为小数分隔符:1234.54。 解析 bar.csv 时应抛出异常,因为它不符合 foo.csv 的模式。

我尝试使用十进制格式,但它没有按预期工作:

    @Test
    void testDecimalFormatParsing() throws ParseException 
        DecimalFormatSymbols sfs = new DecimalFormatSymbols();
        sfs.setDecimalSeparator(',');
        sfs.setGroupingSeparator('.');
        DecimalFormat decimalFormat = new DecimalFormat("#,##0.###", sfs);
        decimalFormat.setGroupingUsed(true);
        decimalFormat.setGroupingSize(3);
        decimalFormat.setParseBigDecimal(true);

        assertEquals(0, parseMoney(decimalFormat, "1.234,56").compareTo(new BigDecimal("1234.56")));
        assertThrows(ParseException.class, () -> parseMoney(decimalFormat, "1234.56"));
    

    private BigDecimal parseMoney(final DecimalFormat decimalFormat, final String originalValue) throws ParseException 
        final ParsePosition position = new ParsePosition(0);
        BigDecimal parsedValue = (BigDecimal) decimalFormat.parse(originalValue, position);
        boolean isParsedSuccesfully = position.getErrorIndex() == -1 && position.getIndex() == originalValue.length();

        System.out.println(originalValue + " -> " + parsedValue + " result: "
                + (isParsedSuccesfully ? "success" : "failure"));

        if (!isParsedSuccesfully) 
            throw new ParseException(originalValue, position.getIndex());
         else 
            return parsedValue;
        
    

这会返回:

1.234,56 -> 1234.56 result: success
1234.56 -> 123456 result: success


org.opentest4j.AssertionFailedError: Expected java.text.ParseException to be thrown, but nothing was thrown.

它只是忽略了分组大小,结果比它应该的大一百倍。因为它不会抛出异常,所以没有人会注意到。

我想我的下一个方法是使用正则表达式。我只是想知道我是否使用了 DecimalFormat 错误?或者您知道实现我想做的更好的方法吗?

【问题讨论】:

解析时忽略分组大小。它在生成数字时使用。 是的,我注意到像1.23.4,56 这样的数字也可以被解析。问题是是否有办法避免这种情况。 【参考方案1】:

当您检查DecimalFormat#parse(...) (source openjdk) 尤其是DecimalFormat#subparse(...) (source openjdk) 的来源时,您会注意到它没有考虑分组大小。相反,它主要用于格式化给定数字。

另外两个验证号码的选项可以是:

正则表达式 格式化解析后的数字,看它是否匹配原始值

后一种方法很简单:

boolean isParsedSuccessfully = decimalFormat.format(parsedValue).equals(originalValue);

使用它而不是您的初始方法会产生这些测试值:

parseMoney(decimalFormat, "1.234,56");
parseMoney(decimalFormat, "1.234.567.890,12");
parseMoney(decimalFormat, "1234.56,0");
parseMoney(decimalFormat, "1.2.3.4.56,0");

这个结果:

1.234,56 -> 1234.56 结果:成功 1.234.567.890,12 -> 1234567890.12 结果:成功 1234.56,0 -> 123456.0 结果:失败 1.2.3.4.56,0 -> 123456.0 结果:失败

【讨论】:

以上是关于解析带有小数和分组分隔符的货币字符串,或者如果 Pattern 不匹配则抛出的主要内容,如果未能解决你的问题,请参考以下文章

DecimalFormat(数字格式)

无法解析逗号分隔的字符串数量。最初带有货币符号

java中 DecimalFormat格式的定义

java中 DecimalFormat格式的定义

DecimalFormat用法

DecimalFormat详解