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

Чета някакъв .csv файл, който съдържа низ, който представлява десетично число. Проблемът ми е, че много пъти получавам запис на файл с различен локал. Например:

  1. Стойността на колоната цена на file1.csv е 129,13 (, е десетичен разделител)
  2. Стойността на цената на колоната на 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)


Въпреки че не можете да зададете нищо (null) за разделителя на групи (тъй като е 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 = нов 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