Проверить десятичное число

Я читаю некоторый файл .csv, содержащий строку, представляющую десятичное число. Моя проблема в том, что много раз я получаю запись файла с другой локалью. Например:

  1. Значение столбца price файла file1.csv равно 129,13 (, является десятичным разделителем)
  2. Значение столбца price файла file1.csv равно 129,13 (. — десятичный разделитель).

Теперь я пытаюсь прочитать значение таким образом:

 DecimalFormatSymbols dfs = new DecimalFormatSymbols(new Locale(en,US));
 DecimalFormat df= new DecimalFormat();
 df.setDecimalFormatSymbols(dfs);
 df.setParseBigDecimal(true);
 bigDecimal = (BigDecimal) df.parse(value);

Используя этот фрагмент кода, первое значение становится 12913 (неверно), а второе — 129,13 (верно). Теперь я хотел бы, чтобы, если я использую en_US local и файл содержит значения, которые используют , например, десятичный разделитель, я должен генерировать исключение.

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


person Skizzo    schedule 16.06.2015    source источник
comment
Вы можете сначала попробовать проверить значение, используя динамически построенное регулярное выражение, используя dfs.getDecimalSeparator(), что для английского языка может привести к выражению, подобному \d+(\.\d{1,2})?, которое позволит использовать целые и десятичные значения, используя точку в качестве десятичного разделителя и допуская от 1 до 2 дробных цифр.   -  person Thomas    schedule 16.06.2015
comment
Немного странно, что у вас нет единого способа форматирования этих чисел. Какой разделитель файла CSV?   -  person Tom    schedule 16.06.2015
comment
@Tom Я не знаю разделителя-разделителя, потому что моя система взаимодействует с внешней системой, которая может генерировать файл в обоих направлениях.   -  person Skizzo    schedule 16.06.2015
comment
Тогда как вы сможете узнать, что , является разделителем между столбцами или символом числа?   -  person Tom    schedule 16.06.2015


Ответы (3)


Хотя вы не можете ничего установить (нуль) для разделителя групп (поскольку это char), при использовании DecimalFormatSymbols вы можете установить что-то очень необычное, чтобы его можно было найти в допустимом числе, например «@».

 DecimalFormatSymbols dfs = new DecimalFormatSymbols(new Locale(en,US));
 dfs.setGroupingSeparator('@');
 DecimalFormat df= new DecimalFormat();
 df.setDecimalFormatSymbols(dfs);
 df.setParseBigDecimal(true);
 bigDecimal = (BigDecimal) df.parse(value);
person Brett Walker    schedule 16.06.2015
comment
Что произойдет, если вы не предоставите Locale конструктору DecimalFormatSymbols, например. DecimalFormatSymbols dfs = new DecimalFormatSymbols(); - person Brett Walker; 16.06.2015
comment
Он принимает локальный по умолчанию, но результат не меняется - person Skizzo; 16.06.2015

Из учебников по Java:

DecimalFormatSymbols unusualSymbols = new DecimalFormatSymbols(currentLocale);
unusualSymbols.setDecimalSeparator('|');
unusualSymbols.setGroupingSeparator('^');

String strange = "#,##0.###";
DecimalFormat weirdFormatter = new DecimalFormat(strange, unusualSymbols);
weirdFormatter.setGroupingSize(4);

Вам нужно установить шаблон DeciamlFormat, чтобы собрать все это вместе.

DecimalFormatSymbols dfs = new DecimalFormatSymbols(new Locale(en,US));
dfs.setGroupingSeparator('@');

DecimalFormat df= new DecimalFormat(#,###.#", dfs);
df.setParseBigDecimal(true);

bigDecimal = (BigDecimal) df.parse(value);
person Brett Walker    schedule 16.06.2015
comment
129,13 становится 129 - person Skizzo; 16.06.2015
comment
Я думаю, что нет красивого решения этой проблемы. OP получает файл CSV с неизвестным разделителем и "," или "." в качестве десятичного разделителя. Он мог бы сделать что-то вроде string.replace(",", ".");, но это далеко не оптимально. Лучшим решением было бы, если бы у OP была информация, какой знак имеет какое значение в текущем файле, или если бы он мог получить эти файлы в единой структуре. - person Tom; 16.06.2015

Вы можете читать файлы CSV с помощью univocity-parsers.

Мы все еще работаем над версией 2.0, в которой представлено автоматическое определение формата, но вы уже можете получить версию моментального снимка и использовать ее для решения этой проблемы.

Простой пример:

public static void main(String... args) {

    CsvParserSettings parserSettings = new CsvParserSettings();
    parserSettings.detectFormatAutomatically();

    List<String[]> rows = new CsvParser(parserSettings).parseAll(new StringReader("Amount,Tax,Total\n1.99,10.0,2.189\n5,20.0,6"));
    for (Object[] row : rows) {
        System.out.println(Arrays.toString(row));
    }

    System.out.println("####");

    rows = new CsvParser(parserSettings).parseAll(new StringReader("Amount;Tax;Total\n1,99;10,0;2,189\n5;20,0;6"));
    for (Object[] row : rows) {
        System.out.println(Arrays.toString(row));
    }
}

Выход:

[Amount, Tax, Total]
[1.99, 10.0, 2.189]
[5, 20.0, 6]
####
[Amount, Tax, Total]
[1,99, 10,0, 2,189]
[5, 20,0, 6]

Вы можете получить последнюю версию моментального снимка по адресу . здесь.

Или, если вы используете maven, добавьте это в свой pom.xml:

<repositories>
    <repository>
        <id>ossrh</id>
        <url>https://oss.sonatype.org/content/repositories/snapshots</url>
    </repository>
</repositories>

И установите версию 2.0.0-SNAPSHOT:

<dependency>
        <groupId>com.univocity</groupId>
        <artifactId>univocity-parsers</artifactId>
        <version>2.0.0-SNAPSHOT</version>
</dependency>

Если вы обнаружите какую-либо проблему, просто откройте новую задачу на странице проекта github.

Изменить: еще один пример, демонстрирующий, как вы можете преобразовать свои входные строки в BigDecimal, используя несколько средств форматирования:

public static void main(String... args) {
    // ObjectRowListProcessor converts the parsed values and stores the result in a list.
    ObjectRowListProcessor rowProcessor = new ObjectRowListProcessor();

    FormattedBigDecimalConversion conversion = new FormattedBigDecimalConversion();
    conversion.addFormat("0.00", "decimalSeparator=.");
    conversion.addFormat("0,00", "decimalSeparator=,");

    // Here we convert fields at columns 0, 1 and 2 to BigDecimal, using two possible input formats 
    rowProcessor.convertIndexes(conversion).set(0, 1, 2);

    // Create a settings object to configure the CSV parser
    CsvParserSettings parserSettings = new CsvParserSettings();

    //I'll separate the values using | to make it easier for you to identify the values in the input
    parserSettings.getFormat().setDelimiter('|');

    // We want to use the RowProcessor configured above to parse our data 
    parserSettings.setRowProcessor(rowProcessor);


    // Create the parser
    CsvParser parser = new CsvParser(parserSettings);

    // Parse everything. All rows are sent to the rowProcessor configured above
    parser.parse(new StringReader("1.99|10.0|2.189\n1,99|10,0|2,189"));

    // Let's get the parsed rows
    List<Object[]> rows = rowProcessor.getRows();
    for (Object[] row : rows) {
        System.out.println(Arrays.toString(row));
    }
}

И вот результат: 2 массива с объектами BigDecimal и правильными значениями:

[1.99, 10.0, 2.189]
[1.99, 10.0, 2.189]
person Jeronimo Backes    schedule 17.06.2015
comment
Может ли этот синтаксический анализатор справиться с ситуацией, когда в CSV нет заголовка, поэтому , или . числа являются первым возможным разделителем, которого он может достичь? - person Tom; 17.06.2015
comment
Да, с этим проблем нет. По умолчанию он все равно не читает заголовки. Я добавил их в свой пример, чтобы его было легче читать. - person Jeronimo Backes; 17.06.2015
comment
Тогда как он решает, какой символ является разделителем? - person Tom; 17.06.2015
comment
Он анализирует часть ввода перед запуском процесса синтаксического анализа. В основном он собирает статистику с появлением символов-кандидатов в нескольких входных строках. Если один и тот же символ встречается во всех (допустимых) строках с небольшой разницей (например, всегда 10-11 запятых в каждой строке), то он выбирается в качестве разделителя. Он также обнаруживает разделитель строк, кавычки и экранирование кавычек. Попробуйте! - person Jeronimo Backes; 17.06.2015