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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/965831/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 21:42:10  来源:igfitidea点击:

How to parse a currency Amount (US or EU) to float value in Java

javacurrency

提问by Sergio del Amo

In Europe decimals are separated with ',' and we use optional '.' to separate thousands. I allow currency values with:

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

  • US-style 123,456.78 notation
  • European-style 123.456,78 notation
  • 美式 123,456.78 符号
  • 欧式 123.456,78 符号

I use the next regular expression (from RegexBuddy library) to validate the input. I allow optional two-digits fractions and optional thousands separators.

我使用下一个正则表达式(来自 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})?)$

I would like to parse a currency string to a float. For example

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

123,456.78 should be stored as 123456.78
123.456,78 should be stored as 123456.78
123.45 should be stored as 123.45
1.234 should be stored as 1234 12.34 should be stored as 12.34

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

and so on...

等等...

Is there an easy way to do this in Java?

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

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

Use BigDecimal instead of Float

使用 BigDecimal 而不是 Float



Thanks to everyone for the great answers. I have changed my code to use BigDecimal instead of float. I will keep previous part of this question with float to prevent people from doing the same mistakes I was gonna do.

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

Solution

解决方案



The next code shows a function which transforms from US and EU currency to a string accepted by BigDecimal(String) constructor. That it is to say a string with no thousand separator and a point for fractions.

下一个代码显示了一个函数,该函数将美国和欧盟货币转换为 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;
        }           
    }  

}

采纳答案by Michael Petrotta

To answer a slightly different question: don'tuse the float type to represent currency values. It will bite you. Use a base-10 type instead, like BigDecimal, or an integer type like intor long(representing the quantum of your value - penny, for example, in US currency).

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

You will not be able to store an exact value - 123.45, say, as a float, and mathematical operations on that value (such as multiplication by a tax percentage) will produce rounding errors.

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

Example from that page:

该页面的示例:

float a = 8250325.12f;
float b = 4321456.31f;
float c = a + b;
System.out.println(NumberFormat.getCurrencyInstance().format(c));
// prints ,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 ,571,781.43 (right)

You don't want to muck with errors when it comes to money.

当涉及到金钱时,您不想犯错误。

With respect to the original question, I haven't touched Java in a little while, but I know that I'd like to stay away from regex to do this kind of work. I see this recommended; it may help you. Not tested; caveat developer.

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

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

Look for a locale with rules that match what you expect to see. If you can't find one, use multiple sequentially, or create your own custom NumberFormat.

寻找具有与您期望看到的内容相匹配的规则的语言环境。如果找不到,请按顺序使用多个,或创建自己的自定义 NumberFormat

I'd also consider forcing users to enter values in a single, canonical format. 123.45 and 123.456 look waytoo similar for my tastes, and by your rules would result in values that differ by a factor of 1000. This is how millions are lost.

我还考虑强制用户以单一、规范的格式输入值。123.45和123.456的外观的方式太相似了,我的口味,并通过你的规则会导致通过的1000倍相差值 这是数以百万计是如何丢失的

回答by Gareth Davis

A quick a dirty hack could be:

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

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.

Note this will only work if the input is in the form ###.00, ie with exactly 2 decimal places. For example input == "10,022"will break this rather naive code.

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

Alternative is to use the BigDecimal(String)constructor, but you'll need to convert those euro style numbers to use '.' as the decimal separator, in addition to removing the thousand separators for both.

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

回答by eatSleepCode

As a generalized solution you can try

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

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

instead of

代替

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

so that fractional part can be of any length.

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

回答by mgooty

Try this.............

尝试这个.............

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