Безопасное преобразование строки в BigDecimal


120

Я пытаюсь прочитать некоторые значения BigDecimal из строки. Скажем, у меня есть строка: «1,000,000,000.999999999999999», и я хочу получить из нее BigDecimal. Как это сделать?

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

Я нашел класс DecimalFormatter, однако, поскольку он работает через double, теряется огромная точность.

Итак, как я могу это сделать?


Потому что с учетом некоторого настраиваемого формата сложно преобразовать ваш формат в формат, совместимый с BigDecimal.
— bezmax

2
«Потому что, учитывая некоторый нестандартный формат, это больно ...» Я не знаю, это как бы разделяет проблемные области. Сначала вы очищаете строку от всего, что читается человеком, а затем передаете ее тому, кто знает, как правильно и эффективно преобразовать результат в файл BigDecimal.
— TJ Crowder,

Ответы:


90

Проверьте setParseBigDecimalв DecimalFormat. С помощью этого установщика parseвернет вам BigDecimal.


1
Конструктор в классе BigDecimal не поддерживает настраиваемые форматы. В примере, который я привел в вопросе, используется настраиваемый формат (запятые). Вы не можете разобрать это на BigDecimal, используя его конструктор.
— bezmax

24
Если вы собираетесь полностью изменить свой ответ, предлагаю упомянуть об этом в ответе. Иначе это выглядит очень странно, когда люди указывают, почему ваш первоначальный ответ не имел никакого смысла. :-)
— TJ Crowder

1
Ага, не заметил такого метода. Спасибо, кажется, это лучший способ сделать это. Сейчас протестирую, и если работает - примите ответ.
— bezmax

15
можешь привести пример?
— J Woodchuck

61
String value = "1,000,000,000.999999999999999";
BigDecimal money = new BigDecimal(value.replaceAll(",", ""));
System.out.println(money);

Полный код, подтверждающий, что не NumberFormatExceptionвыброшено:

import java.math.BigDecimal;

public class Tester {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        String value = "1,000,000,000.999999999999999";
        BigDecimal money = new BigDecimal(value.replaceAll(",", ""));
        System.out.println(money);
    }
}

Вывод

+1000000000,999999999999999


@Steve McLeod .... нет, я только что проверил ... моя ценность1000000000.999999999999999
— Buhake Sindi

10
Попробуйте использовать НЕМЕЦКИЙ языковой стандарт и 1.000.000.000,999999999999
— TofuBeer

@ # TofuBeer .... в примере было 1000000000.999999999999999. Если бы мне пришлось сделать вашу локаль, мне бы пришлось заменить все точки на пробел ....
— Бухак Синди

14
Так что это небезопасно. Вы не знаете всех регионов.
— ymajoros

3
Ничего страшного, если вы просто используете, например, DecimalFormatSymbols.getInstance().getGroupingSeparator()вместо запятой, не нужно волноваться по поводу разных языков.
— Jason C

22

Следующий пример кода работает хорошо (языковой стандарт необходимо получать динамически)

import java.math.BigDecimal;
import java.text.NumberFormat;
import java.text.DecimalFormat;
import java.text.ParsePosition;
import java.util.Locale;

class TestBigDecimal {
    public static void main(String[] args) {

        String str = "0,00";
        Locale in_ID = new Locale("in","ID");
        //Locale in_ID = new Locale("en","US");

        DecimalFormat nf = (DecimalFormat)NumberFormat.getInstance(in_ID);
        nf.setParseBigDecimal(true);

        BigDecimal bd = (BigDecimal)nf.parse(str, new ParsePosition(0));

        System.out.println("bd value : " + bd);
    }
}

6

Код мог бы быть чище, но, похоже, это помогает для разных языков.

import java.math.BigDecimal;
import java.text.DecimalFormatSymbols;
import java.util.Locale;


public class Main
{
    public static void main(String[] args)
    {
        final BigDecimal numberA;
        final BigDecimal numberB;

        numberA = stringToBigDecimal("1,000,000,000.999999999999999", Locale.CANADA);
        numberB = stringToBigDecimal("1.000.000.000,999999999999999", Locale.GERMANY);
        System.out.println(numberA);
        System.out.println(numberB);
    }

    private static BigDecimal stringToBigDecimal(final String formattedString,
                                                 final Locale locale)
    {
        final DecimalFormatSymbols symbols;
        final char                 groupSeparatorChar;
        final String               groupSeparator;
        final char                 decimalSeparatorChar;
        final String               decimalSeparator;
        String                     fixedString;
        final BigDecimal           number;

        symbols              = new DecimalFormatSymbols(locale);
        groupSeparatorChar   = symbols.getGroupingSeparator();
        decimalSeparatorChar = symbols.getDecimalSeparator();

        if(groupSeparatorChar == '.')
        {
            groupSeparator = "\\" + groupSeparatorChar;
        }
        else
        {
            groupSeparator = Character.toString(groupSeparatorChar);
        }

        if(decimalSeparatorChar == '.')
        {
            decimalSeparator = "\\" + decimalSeparatorChar;
        }
        else
        {
            decimalSeparator = Character.toString(decimalSeparatorChar);
        }

        fixedString = formattedString.replaceAll(groupSeparator , "");
        fixedString = fixedString.replaceAll(decimalSeparator , ".");
        number      = new BigDecimal(fixedString);

        return (number);
    }
}

3

Вот как бы я это сделал:

public String cleanDecimalString(String input, boolean americanFormat) {
    if (americanFormat)
        return input.replaceAll(",", "");
    else
        return input.replaceAll(".", "");
}

Очевидно, если бы это происходило в производственном коде, это было бы не так просто.

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


А если строка не в американском формате? В Германии это будет 1.000.000.000,999999999999999
— Стив МакЛауд,

1
Основная проблема здесь в том, что числа могут быть получены из разных источников, каждый из которых имеет свой формат. Таким образом, лучший способ сделать это в этой ситуации - определить некоторый «числовой формат» для каждого из источников. Я не могу сделать это простыми заменами, так как один источник может иметь формат «123 456 789», а другой - «123.456.789».
— bezmax

Я вижу, вы молча переопределяете термин «однострочный метод» ... ;-)
— Петер Торок

@Steve: это довольно фундаментальное различие, которое требует явной обработки, а не подхода "регулярное выражение для всех".
— Майкл Боргвардт,

2
@Max: «Основная проблема здесь в том, что числа могут быть получены из разных источников, каждый из которых имеет свой формат». Который утверждает , за , а не против, ваши очистки введенных данных перед тем как перейти к коду вы не контролируете.
— TJ Crowder

3

Мне нужно было решение для преобразования String в BigDecimal без знания языкового стандарта и независимости от языкового стандарта. Я не смог найти стандартного решения этой проблемы, поэтому написал свой собственный вспомогательный метод. Может быть, и кому-то поможет:

Обновление: Внимание! Этот вспомогательный метод работает только с десятичными числами, поэтому числа всегда имеют десятичную точку! В противном случае вспомогательный метод может выдать неверный результат для чисел от 1000 до 999999 (плюс / минус). Спасибо bezmax за его большой вклад!

static final String EMPTY = "";
static final String POINT = '.';
static final String COMMA = ',';
static final String POINT_AS_STRING = ".";
static final String COMMA_AS_STRING = ",";

/**
     * Converts a String to a BigDecimal.
     *     if there is more than 1 '.', the points are interpreted as thousand-separator and will be removed for conversion
     *     if there is more than 1 ',', the commas are interpreted as thousand-separator and will be removed for conversion
     *  the last '.' or ',' will be interpreted as the separator for the decimal places
     *  () or - in front or in the end will be interpreted as negative number
     *
     * @param value
     * @return The BigDecimal expression of the given string
     */
    public static BigDecimal toBigDecimal(final String value) {
        if (value != null){
            boolean negativeNumber = false;

            if (value.containts("(") && value.contains(")"))
               negativeNumber = true;
            if (value.endsWith("-") || value.startsWith("-"))
               negativeNumber = true;

            String parsedValue = value.replaceAll("[^0-9\\,\\.]", EMPTY);

            if (negativeNumber)
               parsedValue = "-" + parsedValue;

            int lastPointPosition = parsedValue.lastIndexOf(POINT);
            int lastCommaPosition = parsedValue.lastIndexOf(COMMA);

            //handle '1423' case, just a simple number
            if (lastPointPosition == -1 && lastCommaPosition == -1)
                return new BigDecimal(parsedValue);
            //handle '45.3' and '4.550.000' case, only points are in the given String
            if (lastPointPosition > -1 && lastCommaPosition == -1){
                int firstPointPosition = parsedValue.indexOf(POINT);
                if (firstPointPosition != lastPointPosition)
                    return new BigDecimal(parsedValue.replace(POINT_AS_STRING, EMPTY));
                else
                    return new BigDecimal(parsedValue);
            }
            //handle '45,3' and '4,550,000' case, only commas are in the given String
            if (lastPointPosition == -1 && lastCommaPosition > -1){
                int firstCommaPosition = parsedValue.indexOf(COMMA);
                if (firstCommaPosition != lastCommaPosition)
                    return new BigDecimal(parsedValue.replace(COMMA_AS_STRING, EMPTY));
                else
                    return new BigDecimal(parsedValue.replace(COMMA, POINT));
            }
            //handle '2.345,04' case, points are in front of commas
            if (lastPointPosition < lastCommaPosition){
                parsedValue = parsedValue.replace(POINT_AS_STRING, EMPTY);
                return new BigDecimal(parsedValue.replace(COMMA, POINT));
            }
            //handle '2,345.04' case, commas are in front of points
            if (lastCommaPosition < lastPointPosition){
                parsedValue = parsedValue.replace(COMMA_AS_STRING, EMPTY);
                return new BigDecimal(parsedValue);
            }
            throw new NumberFormatException("Unexpected number format. Cannot convert '" + value + "' to BigDecimal.");
        }
        return null;
    }

Конечно, я проверил метод:

@Test(dataProvider = "testBigDecimals")
    public void toBigDecimal_defaultLocaleTest(String stringValue, BigDecimal bigDecimalValue){
        BigDecimal convertedBigDecimal = DecimalHelper.toBigDecimal(stringValue);
        Assert.assertEquals(convertedBigDecimal, bigDecimalValue);
    }
    @DataProvider(name = "testBigDecimals")
    public static Object[][] bigDecimalConvertionTestValues() {
        return new Object[][] {
                {"5", new BigDecimal(5)},
                {"5,3", new BigDecimal("5.3")},
                {"5.3", new BigDecimal("5.3")},
                {"5.000,3", new BigDecimal("5000.3")},
                {"5.000.000,3", new BigDecimal("5000000.3")},
                {"5.000.000", new BigDecimal("5000000")},
                {"5,000.3", new BigDecimal("5000.3")},
                {"5,000,000.3", new BigDecimal("5000000.3")},
                {"5,000,000", new BigDecimal("5000000")},
                {"+5", new BigDecimal("5")},
                {"+5,3", new BigDecimal("5.3")},
                {"+5.3", new BigDecimal("5.3")},
                {"+5.000,3", new BigDecimal("5000.3")},
                {"+5.000.000,3", new BigDecimal("5000000.3")},
                {"+5.000.000", new BigDecimal("5000000")},
                {"+5,000.3", new BigDecimal("5000.3")},
                {"+5,000,000.3", new BigDecimal("5000000.3")},
                {"+5,000,000", new BigDecimal("5000000")},
                {"-5", new BigDecimal("-5")},
                {"-5,3", new BigDecimal("-5.3")},
                {"-5.3", new BigDecimal("-5.3")},
                {"-5.000,3", new BigDecimal("-5000.3")},
                {"-5.000.000,3", new BigDecimal("-5000000.3")},
                {"-5.000.000", new BigDecimal("-5000000")},
                {"-5,000.3", new BigDecimal("-5000.3")},
                {"-5,000,000.3", new BigDecimal("-5000000.3")},
                {"-5,000,000", new BigDecimal("-5000000")},
                {null, null}
        };
    }

1
Извините, но я не понимаю, как это может быть полезно из-за крайних случаев. Например, как узнать, что из себя 7,333представляет? Это 7333 или 7 и 1/3 (7,333)? В разных регионах этот номер будет анализироваться по-разному. Вот доказательство концепции: ideone.com/Ue9rT8
— bezmax

Хороший вклад, никогда не сталкивался с этой проблемой на практике. Я обновлю свой пост, большое спасибо! И я улучшу тестирование этих специальных предложений Locale, чтобы знать, где возникнут проблемы.
— user3227576

1
Я проверил решение для разных локалей и разных чисел ... и для нас все работает, потому что у нас всегда есть числа с десятичной точкой (мы читаем денежные значения из текстового файла). Я добавил к ответу предупреждение.
— user3227576

2
resultString = subjectString.replaceAll("[^.\\d]", "");

удалит все символы, кроме цифр и точки из вашей строки.

Чтобы сделать его зависимым от локали, вы можете использовать getDecimalSeparator()from java.text.DecimalFormatSymbols. Я не знаю Java, но это может выглядеть так:

sep = getDecimalSeparator()
resultString = subjectString.replaceAll("[^"+sep+"\\d]", "");

Что даст неожиданные результаты для неамериканских чисел - см. Комментарии к ответу Джастина.
— Péter Török

2

Старая тема, но, возможно, проще всего использовать Apache Commons NumberUtils, у которого есть метод createBigDecimal (String value) ....

Я предполагаю (надеюсь), что он принимает во внимание локали, иначе это было бы бесполезно.


createBigDecimal - это только оболочка для нового BigDecimal (строка), как указано в исходном коде NumberUtils, который не учитывает языковой стандарт AFAIK
— Mar Bar

1

Пожалуйста, попробуйте, это сработает для меня

BigDecimal bd ;
String value = "2000.00";

bd = new BigDecimal(value);
BigDecimal currency = bd;

2
Этот способ не поддерживает указание настраиваемых разделителей для десятичной точки и групп thouthands.
— bezmax

Да, но это для получения строкового значения BigDecimal из объекта, такого как HashMap, и сохранения его в значении Bigdecimal для вызова других сервисов
— Раджкумар Теккрафт,

1
Да, но вопрос был не в этом.
— bezmax

У меня тоже работает
— Sathesh
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.