mirror of https://github.com/apache/poi.git
Format numbers more like Excel does
Thanks to Chris Boyle for the patch https://bz.apache.org/bugzilla/show_bug.cgi?id=58471 git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1706971 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
72bae7a1e6
commit
940f3c5af3
|
@ -162,11 +162,8 @@ public class DataFormatter implements Observer {
|
|||
*/
|
||||
private DateFormatSymbols dateSymbols;
|
||||
|
||||
/** <em>General</em> format for whole numbers. */
|
||||
private Format generalWholeNumFormat;
|
||||
|
||||
/** <em>General</em> format for decimal numbers. */
|
||||
private Format generalDecimalNumFormat;
|
||||
/** <em>General</em> format for numbers. */
|
||||
private Format generalNumberFormat;
|
||||
|
||||
/** A default format to use when a number pattern cannot be parsed. */
|
||||
private Format defaultNumFormat;
|
||||
|
@ -308,10 +305,7 @@ public class DataFormatter implements Observer {
|
|||
|
||||
// Is it one of the special built in types, General or @?
|
||||
if ("General".equalsIgnoreCase(formatStr) || "@".equals(formatStr)) {
|
||||
if (isWholeNumber(cellValue)) {
|
||||
return generalWholeNumFormat;
|
||||
}
|
||||
return generalDecimalNumFormat;
|
||||
return generalNumberFormat;
|
||||
}
|
||||
|
||||
// Build a formatter, and cache it
|
||||
|
@ -378,10 +372,7 @@ public class DataFormatter implements Observer {
|
|||
}
|
||||
|
||||
if ("General".equalsIgnoreCase(formatStr) || "@".equals(formatStr)) {
|
||||
if (isWholeNumber(cellValue)) {
|
||||
return generalWholeNumFormat;
|
||||
}
|
||||
return generalDecimalNumFormat;
|
||||
return generalNumberFormat;
|
||||
}
|
||||
|
||||
if(DateUtil.isADateFormat(formatIndex,formatStr) &&
|
||||
|
@ -656,15 +647,6 @@ public class DataFormatter implements Observer {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the double value represents a whole number
|
||||
* @param d the double value to check
|
||||
* @return <code>true</code> if d is a whole number
|
||||
*/
|
||||
private static boolean isWholeNumber(double d) {
|
||||
return d == Math.floor(d);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a default format for a cell.
|
||||
* @param cell The cell
|
||||
|
@ -682,10 +664,7 @@ public class DataFormatter implements Observer {
|
|||
|
||||
// otherwise use general format
|
||||
}
|
||||
if (isWholeNumber(cellValue)){
|
||||
return generalWholeNumFormat;
|
||||
}
|
||||
return generalDecimalNumFormat;
|
||||
return generalNumberFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -735,7 +714,8 @@ public class DataFormatter implements Observer {
|
|||
if (numberFormat == null) {
|
||||
return String.valueOf(d);
|
||||
}
|
||||
return numberFormat.format(new Double(d));
|
||||
String formatted = numberFormat.format(new Double(d));
|
||||
return formatted.replaceFirst("E(\\d)", "E+$1"); // to match Excel's E-notation
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -888,8 +868,7 @@ public class DataFormatter implements Observer {
|
|||
Iterator<Map.Entry<String,Format>> itr = formats.entrySet().iterator();
|
||||
while(itr.hasNext()) {
|
||||
Map.Entry<String,Format> entry = itr.next();
|
||||
if (entry.getValue() == generalDecimalNumFormat
|
||||
|| entry.getValue() == generalWholeNumFormat) {
|
||||
if (entry.getValue() == generalNumberFormat) {
|
||||
entry.setValue(format);
|
||||
}
|
||||
}
|
||||
|
@ -969,8 +948,7 @@ public class DataFormatter implements Observer {
|
|||
|
||||
dateSymbols = DateFormatSymbols.getInstance(locale);
|
||||
decimalSymbols = DecimalFormatSymbols.getInstance(locale);
|
||||
generalWholeNumFormat = new DecimalFormat("#", decimalSymbols);
|
||||
generalDecimalNumFormat = new DecimalFormat("#.##########", decimalSymbols);
|
||||
generalNumberFormat = new ExcelGeneralNumberFormat(locale);
|
||||
|
||||
// init built-in formats
|
||||
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
2012 - Alfresco Software, Ltd.
|
||||
Alfresco Software has modified source of this file
|
||||
The details of changes as svn diff can be found in svn at location root/projects/3rd-party/src
|
||||
==================================================================== */
|
||||
package org.apache.poi.ss.usermodel;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.math.RoundingMode;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.text.FieldPosition;
|
||||
import java.text.Format;
|
||||
import java.text.ParsePosition;
|
||||
import java.util.Locale;
|
||||
|
||||
/** A format that formats a double as Excel would, ignoring FieldPosition. All other operations are unsupported. */
|
||||
public class ExcelGeneralNumberFormat extends Format {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final DecimalFormat SCIENTIFIC_FORMAT = new DecimalFormat("0.#####E0");
|
||||
static {
|
||||
SCIENTIFIC_FORMAT.setRoundingMode(RoundingMode.HALF_UP);
|
||||
}
|
||||
private static final MathContext TO_10_SF = new MathContext(10, RoundingMode.HALF_UP);
|
||||
|
||||
private final DecimalFormatSymbols decimalSymbols;
|
||||
private final DecimalFormat wholeNumFormat;
|
||||
private final DecimalFormat decimalNumFormat;
|
||||
|
||||
public ExcelGeneralNumberFormat(final Locale locale) {
|
||||
decimalSymbols = new DecimalFormatSymbols(locale);
|
||||
wholeNumFormat = new DecimalFormat("#", decimalSymbols);
|
||||
DataFormatter.setExcelStyleRoundingMode(wholeNumFormat);
|
||||
decimalNumFormat = new DecimalFormat("#.##########", decimalSymbols);
|
||||
DataFormatter.setExcelStyleRoundingMode(decimalNumFormat);
|
||||
}
|
||||
|
||||
public StringBuffer format(Object number, StringBuffer toAppendTo, FieldPosition pos) {
|
||||
final double value;
|
||||
if (number instanceof Number) {
|
||||
value = ((Number)number).doubleValue();
|
||||
if (Double.isInfinite(value) || Double.isNaN(value)) {
|
||||
return wholeNumFormat.format(number, toAppendTo, pos);
|
||||
}
|
||||
} else {
|
||||
// testBug54786 gets here with a date, so retain previous behaviour
|
||||
return wholeNumFormat.format(number, toAppendTo, pos);
|
||||
}
|
||||
|
||||
final double abs = Math.abs(value);
|
||||
if (abs >= 1E11 || (abs <= 1E-10 && abs > 0)) {
|
||||
return SCIENTIFIC_FORMAT.format(number, toAppendTo, pos);
|
||||
} else if (Math.floor(value) == value || abs >= 1E10) {
|
||||
// integer, or integer portion uses all 11 allowed digits
|
||||
return wholeNumFormat.format(number, toAppendTo, pos);
|
||||
}
|
||||
// Non-integers of non-scientific magnitude are formatted as "up to 11
|
||||
// numeric characters, with the decimal point counting as a numeric
|
||||
// character". We know there is a decimal point, so limit to 10 digits.
|
||||
// https://support.microsoft.com/en-us/kb/65903
|
||||
final double rounded = new BigDecimal(value).round(TO_10_SF).doubleValue();
|
||||
return decimalNumFormat.format(rounded, toAppendTo, pos);
|
||||
}
|
||||
|
||||
public Object parseObject(String source, ParsePosition pos) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
}
|
|
@ -171,7 +171,8 @@ public final class TestHSSFDataFormatter {
|
|||
// create cells with bad num patterns
|
||||
for (int i = 0; i < badNumPatterns.length; i++) {
|
||||
HSSFCell cell = row.createCell(i);
|
||||
cell.setCellValue(1234567890.12345);
|
||||
// If the '.' is any later, ExcelGeneralNumberFormat will render an integer, as Excel does.
|
||||
cell.setCellValue(12345678.9012345);
|
||||
HSSFCellStyle cellStyle = wb.createCellStyle();
|
||||
cellStyle.setDataFormat(format.getFormat(badNumPatterns[i]));
|
||||
cell.setCellStyle(cellStyle);
|
||||
|
@ -276,10 +277,11 @@ public final class TestHSSFDataFormatter {
|
|||
log("\n==== VALID NUMBER FORMATS ====");
|
||||
while (it.hasNext()) {
|
||||
HSSFCell cell = (HSSFCell) it.next();
|
||||
log(formatter.formatCellValue(cell));
|
||||
final String formatted = formatter.formatCellValue(cell);
|
||||
log(formatted);
|
||||
|
||||
// should not be equal to "1234567890.12345"
|
||||
assertTrue( ! "1234567890.12345".equals(formatter.formatCellValue(cell)));
|
||||
// should not include "12345678" - note that the input value was negative
|
||||
assertTrue(formatted != null && ! formatted.contains("12345678"));
|
||||
}
|
||||
|
||||
// test bad number formats
|
||||
|
@ -289,10 +291,9 @@ public final class TestHSSFDataFormatter {
|
|||
while (it.hasNext()) {
|
||||
HSSFCell cell = (HSSFCell) it.next();
|
||||
log(formatter.formatCellValue(cell));
|
||||
// should be equal to "1234567890.12345"
|
||||
// in some locales the the decimal delimiter is a comma, not a dot
|
||||
char decimalSeparator = new DecimalFormatSymbols(LocaleUtil.getUserLocale()).getDecimalSeparator();
|
||||
assertEquals("1234567890" + decimalSeparator + "12345", formatter.formatCellValue(cell));
|
||||
assertEquals("12345678" + decimalSeparator + "9", formatter.formatCellValue(cell));
|
||||
}
|
||||
|
||||
// test Zip+4 format
|
||||
|
|
|
@ -660,4 +660,59 @@ public class TestDataFormatter {
|
|||
assertTrue(DateUtil.isADateFormat(-1, "dd/mm/yy;[red]dd/mm/yy"));
|
||||
assertTrue(DateUtil.isADateFormat(-1, "[h]"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testLargeNumbersAndENotation() throws IOException{
|
||||
assertFormatsTo("1E+86", 99999999999999999999999999999999999999999999999999999999999999999999999999999999999999d);
|
||||
assertFormatsTo("1E-84", 0.000000000000000000000000000000000000000000000000000000000000000000000000000000000001d);
|
||||
// Smallest double
|
||||
assertFormatsTo("1E-323", 0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001d);
|
||||
|
||||
// "up to 11 numeric characters, with the decimal point counting as a numeric character"
|
||||
// https://support.microsoft.com/en-us/kb/65903
|
||||
assertFormatsTo( "12345678911", 12345678911d);
|
||||
assertFormatsTo( "1.23457E+11", 123456789112d); // 12th digit of integer -> scientific
|
||||
assertFormatsTo( "-12345678911", -12345678911d);
|
||||
assertFormatsTo( "-1.23457E+11", -123456789112d);
|
||||
assertFormatsTo( "0.1", 0.1);
|
||||
assertFormatsTo( "0.000000001", 0.000000001);
|
||||
assertFormatsTo( "1E-10", 0.0000000001); // 12th digit
|
||||
assertFormatsTo( "-0.000000001", -0.000000001);
|
||||
assertFormatsTo( "-1E-10", -0.0000000001);
|
||||
assertFormatsTo( "123.4567892", 123.45678919); // excess decimals are simply rounded away
|
||||
assertFormatsTo("-123.4567892", -123.45678919);
|
||||
assertFormatsTo( "1.234567893", 1.2345678925); // rounding mode is half-up
|
||||
assertFormatsTo("-1.234567893", -1.2345678925);
|
||||
assertFormatsTo( "1.23457E+19", 12345650000000000000d);
|
||||
assertFormatsTo("-1.23457E+19", -12345650000000000000d);
|
||||
assertFormatsTo( "1.23457E-19", 0.0000000000000000001234565d);
|
||||
assertFormatsTo("-1.23457E-19", -0.0000000000000000001234565d);
|
||||
assertFormatsTo( "1.000000001", 1.000000001);
|
||||
assertFormatsTo( "1", 1.0000000001);
|
||||
assertFormatsTo( "1234.567891", 1234.567891123456789d);
|
||||
assertFormatsTo( "1234567.891", 1234567.891123456789d);
|
||||
assertFormatsTo( "12345678912", 12345678911.63456789d); // integer portion uses all 11 digits
|
||||
assertFormatsTo( "12345678913", 12345678912.5d); // half-up here too
|
||||
assertFormatsTo("-12345678913", -12345678912.5d);
|
||||
assertFormatsTo( "1.23457E+11", 123456789112.3456789d);
|
||||
}
|
||||
|
||||
private static void assertFormatsTo(String expected, double input) throws IOException {
|
||||
Workbook wb = new HSSFWorkbook();
|
||||
try {
|
||||
Sheet s1 = wb.createSheet();
|
||||
Row row = s1.createRow(0);
|
||||
Cell rawValue = row.createCell(0);
|
||||
rawValue.setCellValue(input);
|
||||
CellStyle newStyle = wb.createCellStyle();
|
||||
DataFormat dataFormat = wb.createDataFormat();
|
||||
newStyle.setDataFormat(dataFormat.getFormat("General"));
|
||||
String actual = new DataFormatter().formatCellValue(rawValue);
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
finally {
|
||||
wb.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue