Как анализировать валюту Сумма (США или ЕС) для плавающей стоимости в Java

В Европе десятичные знаки разделяются ",", и мы используем необязательные "." для разделения тысяч. Я допускаю валютные значения с помощью:

  • Обозначение в США 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})?)$

Я хотел бы проанализировать строку валюты для float. Например

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. Я продолжу предыдущую часть этого вопроса с помощью 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

Чтобы ответить на несколько иной вопрос: не используйте тип float для представления значений валюты. Он вас укусит. Вместо этого используйте тип base-10, например BigDecimal, или целочисленный тип типа int или long (представляющий собой квант вашей стоимости - например, в валюте США).

Вы не сможете сохранить точное значение - 123.45, скажем, как float, а математические операции над этим значением (например, умножение на процентный процент) вызовут ошибки округления.

Пример из этой страницы:

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 через некоторое время, но я знаю, что я бы хотел остаться в стороне от регулярного выражения, чтобы выполнить такую ​​работу. Я вижу, что это рекомендуется; это может помочь вам. Не испытано; разработчик caveat.

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
}

Ищите локаль с правилами, которые соответствуют тому, что вы ожидаете увидеть. Если вы не можете найти его, используйте несколько последовательно или создайте собственный пользовательский NumberFormat.

Я также хотел бы заставить пользователей вводить значения в одном каноническом формате. 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);
}

так что дробная часть может быть любой длины.

Ответ 3

Быстрая грязная ручка может быть:

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), но вам нужно будет преобразовать эти номера в стиле евро, чтобы использовать '.' как десятичный разделитель, в дополнение к удалению тысяч разделителей для обоих.