Как проанализировать сумму в валюте (США или ЕС) для вычисления значения в Java - PullRequest
12 голосов
/ 08 июня 2009

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

  • в стиле США 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. Я оставлю предыдущую часть этого вопроса с помощью 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;
        }           
    }  

}

Ответы [ 4 ]

35 голосов
/ 08 июня 2009

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

Вы не сможете сохранить точное значение - скажем, 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
}

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

Я бы также подумал о том, чтобы заставлять пользователей вводить значения в едином каноническом формате. 123.45 и 123.456 выглядят в порядке слишком похожими для моих вкусов, и по вашим правилам это приведет к значениям, которые отличаются в 1000 раз. Вот как теряются миллионы .

1 голос
/ 29 декабря 2013

В качестве обобщенного решения вы можете попробовать

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

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

0 голосов
/ 18 октября 2018

Попробуйте это .............

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();
            }
        }
0 голосов
/ 08 июня 2009

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

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, то есть с точно двумя десятичными знаками. Например, input == "10,022" сломает этот довольно наивный код.

Альтернативой является использование конструктора BigDecimal(String), но вам нужно преобразовать эти числа в стиле евро, чтобы использовать '.' в качестве десятичного разделителя, в дополнение к удалению тысячи разделителей для обоих.

...