【问题标题】:How to parse a currency Amount (US or EU) to float value in Java如何解析货币金额(美国或欧盟)以在 Java 中浮动值
【发布时间】:2010-11-01 05:42:26
【问题描述】:

在欧洲,小数点用“,”分隔,我们使用可选的“.”分隔千位。我允许使用以下货币值:

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

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

^[+-]?[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.23 转换成什么?你的规则集是矛盾的。为什么不利用应用程序客户端的本地化功能?
  • 好吧,我的错……但说真的,在不知道它来自的语言环境的情况下试图破译它有一股难闻的气味。
  • 嗯,基本上我最感兴趣的是解决欧盟符号样式的问题。但是最好有一个标准的解决方案。
  • @sergio:更新了我的答案。尝试现有的特定于语言环境的 NumberFormats,或使用您期望的格式创建一个自定义的。

标签: java currency


【解决方案1】:

回答一个稍微不同的问题:不要使用浮点类型来表示货币值。 It will bite you。请改用基数为 10 的类型,例如 BigDecimal,或整数类型,例如 intlong(表示您的价值量 - 例如,以美元表示的美分)。

您将无法存储精确的值 - 例如,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
}

寻找具有符合您期望看到的规则的语言环境。如果找不到,请按顺序使用多个,或创建自己的custom NumberFormat

我还考虑强制用户以单一规范格式输入值。 123.45 和 123.456 在我看来方式太相似了,根据你的规则,它们的值会相差 1000 倍。This is how millions are lost

【讨论】:

  • 让我看看,如果我理解你的话。您的意思是我应该使用 int 并将值以美分的形式存储在我的数据库中。 12.35 为 12 * 100 + 35 = 1235。
  • 这听起来很聪明,但我仍然需要一个函数来将货币字符串转换为美分
  • 你会的,是的。我上面写的并没有解决这个问题。我看到你建议使用浮动来处理金钱,然后警钟就响了。
  • 认真听这个家伙的,用花车赚钱有时会伤害你。
  • 是的,这是正确的。有关进一步的讨论,以及事情如何出错的示例,请参阅 Effective Java(第 2 版),第 48 条,“如果需要准确的答案,请避免使用浮点数和双精度数”。引用一些要点:“float 和 double 类型特别不适合货币计算 [...]”,“解决这个问题的正确方法是使用 BigDecimal、int 或 long 进行货币计算。”如果我没记错的话,Java Puzzlers 这本书也在其中一个谜题中处理了这个问题。
【解决方案2】:

一个快速的肮脏黑客可能是:

String input = input.replaceAll("\.,",""); // remove *any* , or .
long amount = Long.parseLong(input);

BigDecimal bd = BigDecimal.valueOf(amount).movePointLeft(2);

//then you could use:
bd.floatValue();
//but I would seriously recommended that you don't use floats for monetary amounts.

请注意,这仅适用于输入格式为###.00 的情况,即恰好有2 个小数位。例如input == "10,022" 会破坏这个相当幼稚的代码。

替代方法是使用 BigDecimal(String) 构造函数,但您需要将这些欧元样式数字转换为使用 '.'作为小数分隔符,除了删除两者的千位分隔符。

【讨论】:

  • >替代方案是使用 BigDecimal(String) 构造函数,但您需要将这些欧元样式数字转换为使用 '.'作为小数点分隔符,除了删除两者的千位分隔符。我可以做一个正则表达式来替换,加上最后两个数字。加上最后两位数字,我可以将所有点和逗号替换为除了 .在最后两位数字之前。我应该工作,但似乎容易出错。
【解决方案3】:

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

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

所以小数部分可以是任意长度。

【讨论】:

    【解决方案4】:

    试试这个......

    Locale slLocale = new Locale("de","DE");
            NumberFormat nf5 = NumberFormat.getInstance(slLocale);
            if(nf5 instanceof DecimalFormat) {
                DecimalFormat df5 = (DecimalFormat)nf5;
                try {
                DecimalFormatSymbols decimalFormatSymbols = DecimalFormatSymbols.getInstance(slLocale);
                decimalFormatSymbols.setGroupingSeparator('.');
                decimalFormatSymbols.setDecimalSeparator(',');
                df5.setDecimalFormatSymbols(decimalFormatSymbols);
                df5.setParseBigDecimal(true);
                ParsePosition pPosition = new ParsePosition(0);
                BigDecimal n = (BigDecimal)df5.parseObject("3.321.234,56", pPosition);
                System.out.println(n);
                }catch(Exception exp) {
                    exp.printStackTrace();
                }
            }
    

    【讨论】:

      猜你喜欢
      • 2012-01-29
      • 2023-04-02
      • 1970-01-01
      • 2021-03-15
      • 2018-03-03
      • 2015-12-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多