Java POI FormulaEvaluator, дающий неожиданное значение с плавающей запятой - PullRequest
0 голосов
/ 01 ноября 2019

Я использую библиотеку Java POI, чтобы прочитать файл Excel и затем отобразить его в таблице HTML. Файл Excel очень прост с 1 строкой и 3 столбцами:

Ячейка A1 = 21,7 Ячейка B1 = 20,0 Ячейка C1 - это ячейка формулы с формулой = (A1-B1) / B1, и она имеет собственный формат«Процент» с 0 десятичных знаков. Excel отображает его значение как 9%. Это потому, что 1,7 / 20 на калькуляторе дает результат как 0,085;когда он конвертируется в формат «Процент», он становится 8,5%, а поскольку в формате указано включение 0 десятичных разрядов, он округляется до 9%, поэтому Excel отображает его. Все хорошо.

Однако POI отображает значение как 8%. Я заметил, что 1.7 / 20 рассчитывается как 0.084999999. Из-за примененного выше формата «Процент» он преобразуется в 8,4999999%, а из-за 0 десятичных разрядов округляется до 8%.

Как получить POI, возвращающую мне 9% вместо 8%? Вот фрагмент кода:

String myFormat="0%";
CreationHelper helper = wbWrapper.getWb().getCreationHelper();
CellUtil.setCellStyleProperty(cell, CellUtil.DATA_FORMAT,helper.createDataFormat().getFormat(myFormat));
String val = dataFormatter.formatCellValue(cell, evaluator);

Здесь оценщик является экземпляром org.apache.poi.ss.usermodel.FormulaEvaluator, а dataFormatter является экземпляром org.apache.poi.ss.usermodel.DataFormatter

Когда я печатаю переменную "val", она возвращает 8% вместо того, что отображается в Excel (9%).

1 Ответ

1 голос
/ 02 ноября 2019

Ваши наблюдения верны. Проблема возникает из-за общих проблем с плавающей точкой. Можно просто показать:

...
System.out.println(1.7/20.0); //0.08499999999999999
System.out.println((21.7-20.0)/20.0); //0.08499999999999996
...

Как видите, деление двойного значения 1,7 на двойное значение 20,0 приводит к 0,08499999999999999. Это было бы хорошо, поскольку это значение было бы принято равным 0,085, используя DecimalFormat. Но более сложное уравнение (21,7-20,0) / 20,0 приводит к 0,08499999999999996. И это явно ниже, чем 0,085.

Excel пытается решить эти проблемы с помощью дополнительного правила для значений с плавающей запятой. Он всегда использует только 15 значащих десятичных цифр значения с плавающей запятой. Так что Excel делает что-то вроде:

...
BigDecimal bd = new BigDecimal((21.7-20.0)/20.0);
System.out.println(bd.round(new MathContext(15)).doubleValue()); //0.085
...

Ни apache poi FormulaEvaluator, ни DataFormatter не ведут себя как Excel в этом пункте. Вот почему разница.

Можно иметь собственный MyDataFormatter, где единственное отличие от / org / apache / poi / ss / usermodel / DataFormatter.java :

...
    private String getFormattedNumberString(Cell cell, ConditionalFormattingEvaluator cfEvaluator) {
        if (cell == null) {
            return null;
        }
        Format numberFormat = getFormat(cell, cfEvaluator);

        double d = cell.getNumericCellValue();
        java.math.BigDecimal bd = new java.math.BigDecimal(d);
        d = bd.round(new java.math.MathContext(15)).doubleValue();

        if (numberFormat == null) {
            return String.valueOf(d);
        }
        String formatted = numberFormat.format(Double.valueOf(d));
        return formatted.replaceFirst("E(\\d)", "E+$1"); // to match Excel's E-notation
    }
...

Тогда использование этого MyDataFormatter вместо DataFormatter будет более совместимым с поведением Excel.

Пример:

import java.io.FileOutputStream;

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

class CreateExcelEvaluateFormula {

 public static void main(String[] args) throws Exception {

  Workbook workbook  = new XSSFWorkbook();
  CreationHelper creationHelper = workbook.getCreationHelper();
  FormulaEvaluator formulaEvaluator = creationHelper.createFormulaEvaluator();

  Sheet sheet = workbook.createSheet();
  Row row = sheet.createRow(0);
  Cell cell = row.createCell(0); cell.setCellValue(21.7);
  cell = row.createCell(1); cell.setCellValue(20.0);

  cell = row.createCell(2); cell.setCellFormula("(A1-B1)/B1");
  formulaEvaluator.evaluateFormulaCell(cell); 
  double d = cell.getNumericCellValue();
System.out.println(d); //0.08499999999999996

  MyDataFormatter dataFormatter = new MyDataFormatter();

  String myFormat="0%";
  CellUtil.setCellStyleProperty(cell, CellUtil.DATA_FORMAT, creationHelper.createDataFormat().getFormat(myFormat));
  String val = dataFormatter.formatCellValue(cell, formulaEvaluator);
  System.out.println(val); //9%

  FileOutputStream out = new FileOutputStream("Excel.xlsx");
  workbook.write(out);
  out.close();
  workbook.close();

 }
}
...