如何在 Java 中解析货币金额(美国或欧盟)以浮点值

2022-09-03 04:18:55

在欧洲,小数以''分隔,我们使用可选的'.'来分隔数千。我允许货币值:

  • 美式 123,456.78 表示法
  • 欧式 123.456,78 表示法

我使用下一个正则表达式(来自正则表达式Buddy库)来验证输入。我允许可选的两位数分数和可选的千位分隔符。

^[+-]?[0-9]{1,3}(?:[0-9]*(?:[.,][0-9]{0,2})?|(?:,[0-9]{3})*(?:\.[0-9]{0,2})?|(?:\.[0-9]{3})*(?:,[0-9]{0,2})?)$

我想将货币字符串解析为浮点数。例如

123,456.78 应存储为 123456.78
123.456,78 应存储为 123456.78
123.45 应存储为 123.45
1.234 应存储为 1234 12.34 应存储为 12.34

等等...

有没有一种简单的方法可以在Java中做到这一点?

public float currencyToFloat(String currency) {
    // transform and return as float
}

使用 BigDecimal 而不是 Float


感谢大家的精彩回答。我已将代码更改为使用 BigDecimal 而不是 float。我将用浮动保留这个问题的前一部分,以防止人们犯我打算犯的同样的错误。

溶液


下一个代码显示了一个函数,该函数从美国和欧盟货币转换为 BigDecimal(String) 构造函数接受的字符串。也就是说,它是一个没有千位分隔符的字符串和一个分数点。

   import java.util.regex.Matcher;
import java.util.regex.Pattern;


public class TestUSAndEUCurrency {

    public static void main(String[] args) throws Exception {       
        test("123,456.78","123456.78");
        test("123.456,78","123456.78");
        test("123.45","123.45");
        test("1.234","1234");
        test("12","12");
        test("12.1","12.1");
        test("1.13","1.13");
        test("1.1","1.1");
        test("1,2","1.2");
        test("1","1");              
    }

    public static void test(String value, String expected_output) throws Exception {
        String output = currencyToBigDecimalFormat(value);
        if(!output.equals(expected_output)) {
            System.out.println("ERROR expected: " + expected_output + " output " + output);
        }
    }

    public static String currencyToBigDecimalFormat(String currency) throws Exception {

        if(!doesMatch(currency,"^[+-]?[0-9]{1,3}(?:[0-9]*(?:[.,][0-9]{0,2})?|(?:,[0-9]{3})*(?:\\.[0-9]{0,2})?|(?:\\.[0-9]{3})*(?:,[0-9]{0,2})?)$"))
                throw new Exception("Currency in wrong format " + currency);

        // Replace all dots with commas
        currency = currency.replaceAll("\\.", ",");

        // If fractions exist, the separator must be a .
        if(currency.length()>=3) {
            char[] chars = currency.toCharArray();
            if(chars[chars.length-2] == ',') {
                chars[chars.length-2] = '.';
            } else if(chars[chars.length-3] == ',') {
                chars[chars.length-3] = '.';
            }
            currency = new String(chars);
        }

        // Remove all commas        
        return currency.replaceAll(",", "");                
    }

    public static boolean doesMatch(String s, String pattern) {
        try {
            Pattern patt = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE);
            Matcher matcher = patt.matcher(s);
            return matcher.matches();
        } catch (RuntimeException e) {
            return false;
        }           
    }  

}

答案 1

回答一个稍微不同的问题:不要使用浮点型来表示货币值。它会咬你。请改用以 10 为基数的类型,如 或 整数类型,如 或 (表示值的数量 - 便士,例如,以美元为单位)。BigDecimalintlong

您将无法存储确切的值 - 例如,123.45 作为浮点数,并且对该值的数学运算(例如乘以税收百分比)将产生舍入误差。

该页面的示例:

float a = 8250325.12f;
float b = 4321456.31f;
float c = a + b;
System.out.println(NumberFormat.getCurrencyInstance().format(c));
// prints $12,571,782.00 (wrong)

BigDecimal a1 = new BigDecimal("8250325.12");
BigDecimal b1 = new BigDecimal("4321456.31");
BigDecimal c1 = a1.add(b1);
System.out.println(NumberFormat.getCurrencyInstance().format(c1));
// prints $12,571,781.43 (right)

在金钱方面,你不想犯错误。

关于最初的问题,我有一段时间没有接触Java了,但我知道我想远离正则表达式来做这种工作。我看到这个推荐;它可能会帮助你。未经测试;警告开发人员。

try {
    String string = NumberFormat.getCurrencyInstance(Locale.GERMANY)
                                            .format(123.45);
    Number number = NumberFormat.getCurrencyInstance(locale)
                                            .parse("$123.45");
    // 123.45
    if (number instanceof Long) {
       // Long value
    } else {
       // too large for long - may want to handle as error
    }
} catch (ParseException e) {
// handle
}

查找具有与你预期看到的内容匹配的规则的区域设置。如果找不到一个,请按顺序使用多个,或创建自己的自定义数字格式

我还会考虑强制用户以单一的规范格式输入值。123.45 和 123.456 看起来太相似了,不符合我的口味,按照你的规则,会导致值相差 1000 倍。数百万人就是这样失去的


答案 2

作为一个通用的解决方案,你可以尝试

char[] chars = currency.toCharArray();
chars[currency.lastIndexOf(',')] = '.';
currency = new String(chars);

而不是

if(currency.length()>=3) {
    char[] chars = currency.toCharArray();
    if(chars[chars.length-2] == ',') {
        chars[chars.length-2] = '.';
    } else if(chars[chars.length-3] == ',') {
        chars[chars.length-3] = '.';
    }
    currency = new String(chars);
}

因此,该分数部分可以是任何长度。