View | Details | Raw Unified | Return to bug 58471
Collapse All | Expand All

(-)a/src/java/org/apache/poi/ss/usermodel/DataFormatter.java (-22 / +9 lines)
Lines 162-172 public class DataFormatter implements Observer { Link Here
162
     */
162
     */
163
    private DateFormatSymbols dateSymbols;
163
    private DateFormatSymbols dateSymbols;
164
164
165
    /** <em>General</em> format for whole numbers. */
165
    /** <em>General</em> format for numbers. */
166
    private Format generalWholeNumFormat;
166
    private Format generalNumberFormat;
167
168
    /** <em>General</em> format for decimal numbers. */
169
    private Format generalDecimalNumFormat;
170
167
171
    /** A default format to use when a number pattern cannot be parsed. */
168
    /** A default format to use when a number pattern cannot be parsed. */
172
    private Format defaultNumFormat;
169
    private Format defaultNumFormat;
Lines 308-317 public class DataFormatter implements Observer { Link Here
308
        
305
        
309
        // Is it one of the special built in types, General or @?
306
        // Is it one of the special built in types, General or @?
310
        if ("General".equalsIgnoreCase(formatStr) || "@".equals(formatStr)) {
307
        if ("General".equalsIgnoreCase(formatStr) || "@".equals(formatStr)) {
311
            if (isWholeNumber(cellValue)) {
308
            return generalNumberFormat;
312
                return generalWholeNumFormat;
313
            }
314
            return generalDecimalNumFormat;
315
        }
309
        }
316
        
310
        
317
        // Build a formatter, and cache it
311
        // Build a formatter, and cache it
Lines 378-387 public class DataFormatter implements Observer { Link Here
378
        }
372
        }
379
        
373
        
380
        if ("General".equalsIgnoreCase(formatStr) || "@".equals(formatStr)) {
374
        if ("General".equalsIgnoreCase(formatStr) || "@".equals(formatStr)) {
381
           if (isWholeNumber(cellValue)) {
375
           return generalNumberFormat;
382
               return generalWholeNumFormat;
383
           }
384
           return generalDecimalNumFormat;
385
        }
376
        }
386
377
387
        if(DateUtil.isADateFormat(formatIndex,formatStr) &&
378
        if(DateUtil.isADateFormat(formatIndex,formatStr) &&
Lines 682-691 public class DataFormatter implements Observer { Link Here
682
673
683
          // otherwise use general format
674
          // otherwise use general format
684
        }
675
        }
685
        if (isWholeNumber(cellValue)){
676
        return generalNumberFormat;
686
            return generalWholeNumFormat;
687
        }
688
        return generalDecimalNumFormat;
689
    }
677
    }
690
    
678
    
691
    /**
679
    /**
Lines 735-741 public class DataFormatter implements Observer { Link Here
735
        if (numberFormat == null) {
723
        if (numberFormat == null) {
736
            return String.valueOf(d);
724
            return String.valueOf(d);
737
        }
725
        }
738
        return numberFormat.format(new Double(d));
726
        String formatted = numberFormat.format(new Double(d));
727
        return formatted.replaceFirst("E(\\d)", "E+$1"); // to match Excel's E-notation
739
    }
728
    }
740
729
741
    /**
730
    /**
Lines 888-895 public class DataFormatter implements Observer { Link Here
888
        Iterator<Map.Entry<String,Format>> itr = formats.entrySet().iterator();
877
        Iterator<Map.Entry<String,Format>> itr = formats.entrySet().iterator();
889
        while(itr.hasNext()) {
878
        while(itr.hasNext()) {
890
            Map.Entry<String,Format> entry = itr.next();
879
            Map.Entry<String,Format> entry = itr.next();
891
            if (entry.getValue() == generalDecimalNumFormat
880
            if (entry.getValue() == generalNumberFormat) {
892
                    || entry.getValue() == generalWholeNumFormat) {
893
                entry.setValue(format);
881
                entry.setValue(format);
894
            }
882
            }
895
        }
883
        }
Lines 969-976 public class DataFormatter implements Observer { Link Here
969
        
957
        
970
        dateSymbols = DateFormatSymbols.getInstance(locale);
958
        dateSymbols = DateFormatSymbols.getInstance(locale);
971
        decimalSymbols = DecimalFormatSymbols.getInstance(locale);
959
        decimalSymbols = DecimalFormatSymbols.getInstance(locale);
972
        generalWholeNumFormat = new DecimalFormat("#", decimalSymbols);
960
        generalNumberFormat = new ExcelGeneralNumberFormat(locale);
973
        generalDecimalNumFormat = new DecimalFormat("#.##########", decimalSymbols);
974
961
975
        // init built-in formats
962
        // init built-in formats
976
963
(-)a/src/java/org/apache/poi/ss/usermodel/ExcelGeneralNumberFormat.java (+65 lines)
Line 0 Link Here
1
package org.apache.poi.ss.usermodel;
2
3
import java.math.BigDecimal;
4
import java.math.MathContext;
5
import java.math.RoundingMode;
6
import java.text.DecimalFormat;
7
import java.text.DecimalFormatSymbols;
8
import java.text.FieldPosition;
9
import java.text.Format;
10
import java.text.ParsePosition;
11
import java.util.Locale;
12
13
/** A format that formats a double as Excel would, ignoring FieldPosition. All other operations are unsupported. */
14
public class ExcelGeneralNumberFormat extends Format {
15
16
    private static final long serialVersionUID = 1L;
17
18
    private static final DecimalFormat SCIENTIFIC_FORMAT = new DecimalFormat("0.#####E0");
19
    static {
20
        SCIENTIFIC_FORMAT.setRoundingMode(RoundingMode.HALF_UP);
21
    }
22
    private static final MathContext TO_10_SF = new MathContext(10, RoundingMode.HALF_UP);
23
24
    private final DecimalFormatSymbols decimalSymbols;
25
    private final Format wholeNumFormat;
26
    private final Format decimalNumFormat;
27
28
    public ExcelGeneralNumberFormat(final Locale locale) {
29
        decimalSymbols = new DecimalFormatSymbols(locale);
30
        wholeNumFormat = new DecimalFormat("#", decimalSymbols);
31
        decimalNumFormat = new DecimalFormat("#.##########", decimalSymbols);
32
    }
33
34
    public StringBuffer format(Object number, StringBuffer toAppendTo, FieldPosition pos) {
35
        final double value;
36
        if (number instanceof Number) {
37
            value = ((Number)number).doubleValue();
38
            if (Double.isInfinite(value) || Double.isNaN(value)) {
39
                return wholeNumFormat.format(number, toAppendTo, pos);
40
            }
41
        } else {
42
            // testBug54786 gets here with a date, so retain previous behaviour
43
            return wholeNumFormat.format(number, toAppendTo, pos);
44
        }
45
46
        final double abs = Math.abs(value);
47
        if (abs >= 1E11 || (abs <= 1E-10 && abs > 0)) {
48
            return SCIENTIFIC_FORMAT.format(number, toAppendTo, pos);
49
        } else if (Math.floor(value) == value || abs >= 1E10) {
50
            // integer, or integer portion uses all 11 allowed digits
51
            return wholeNumFormat.format(number, toAppendTo, pos);
52
        }
53
        // Non-integers of non-scientific magnitude are formatted as "up to 11
54
        // numeric characters, with the decimal point counting as a numeric
55
        // character". We know there is a decimal point, so limit to 10 digits.
56
        // https://support.microsoft.com/en-us/kb/65903
57
        final double rounded = new BigDecimal(value).round(TO_10_SF).doubleValue();
58
        return decimalNumFormat.format(rounded, toAppendTo, pos);
59
    }
60
61
    public Object parseObject(String source, ParsePosition pos) {
62
        throw new UnsupportedOperationException();
63
    }
64
65
}
(-)a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFDataFormatter.java (-5 / +7 lines)
Lines 171-177 public final class TestHSSFDataFormatter { Link Here
171
		// create cells with bad num patterns
171
		// create cells with bad num patterns
172
		for (int i = 0; i < badNumPatterns.length; i++) {
172
		for (int i = 0; i < badNumPatterns.length; i++) {
173
			HSSFCell cell = row.createCell(i);
173
			HSSFCell cell = row.createCell(i);
174
			cell.setCellValue(1234567890.12345);
174
			// If the '.' is any later, ExcelGeneralNumberFormat will render an integer, as Excel does.
175
			cell.setCellValue(12345678.9012345);
175
			HSSFCellStyle cellStyle = wb.createCellStyle();
176
			HSSFCellStyle cellStyle = wb.createCellStyle();
176
			cellStyle.setDataFormat(format.getFormat(badNumPatterns[i]));
177
			cellStyle.setDataFormat(format.getFormat(badNumPatterns[i]));
177
			cell.setCellStyle(cellStyle);
178
			cell.setCellStyle(cellStyle);
Lines 276-285 public final class TestHSSFDataFormatter { Link Here
276
		log("\n==== VALID NUMBER FORMATS ====");
277
		log("\n==== VALID NUMBER FORMATS ====");
277
		while (it.hasNext()) {
278
		while (it.hasNext()) {
278
			HSSFCell cell = (HSSFCell) it.next();
279
			HSSFCell cell = (HSSFCell) it.next();
279
			log(formatter.formatCellValue(cell));
280
			final String formatted = formatter.formatCellValue(cell);
281
			log(formatted);
280
282
281
			// should not be equal to "1234567890.12345"
283
			// should not include "12345678" - note that the input value was negative
282
			assertTrue( ! "1234567890.12345".equals(formatter.formatCellValue(cell)));
284
			assertTrue(formatted != null && ! formatted.contains("12345678"));
283
		}
285
		}
284
286
285
		// test bad number formats
287
		// test bad number formats
Lines 292-298 public final class TestHSSFDataFormatter { Link Here
292
			// should be equal to "1234567890.12345" 
294
			// should be equal to "1234567890.12345" 
293
			// in some locales the the decimal delimiter is a comma, not a dot
295
			// in some locales the the decimal delimiter is a comma, not a dot
294
			char decimalSeparator = new DecimalFormatSymbols(LocaleUtil.getUserLocale()).getDecimalSeparator();
296
			char decimalSeparator = new DecimalFormatSymbols(LocaleUtil.getUserLocale()).getDecimalSeparator();
295
			assertEquals("1234567890" + decimalSeparator + "12345", formatter.formatCellValue(cell));
297
			assertEquals("12345678" + decimalSeparator + "9", formatter.formatCellValue(cell));
296
		}
298
		}
297
299
298
		// test Zip+4 format
300
		// test Zip+4 format
(-)a/src/testcases/org/apache/poi/ss/usermodel/TestDataFormatter.java (-1 / +53 lines)
Lines 660-663 public class TestDataFormatter { Link Here
660
        assertTrue(DateUtil.isADateFormat(-1, "dd/mm/yy;[red]dd/mm/yy"));
660
        assertTrue(DateUtil.isADateFormat(-1, "dd/mm/yy;[red]dd/mm/yy"));
661
        assertTrue(DateUtil.isADateFormat(-1, "[h]"));
661
        assertTrue(DateUtil.isADateFormat(-1, "[h]"));
662
	}
662
	}
663
664
    public void testLargeNumbersAndENotation() throws IOException{
665
      assertFormatsTo("1E+86", 99999999999999999999999999999999999999999999999999999999999999999999999999999999999999d);
666
      assertFormatsTo("1E-84", 0.000000000000000000000000000000000000000000000000000000000000000000000000000000000001d);
667
      // Smallest double
668
      assertFormatsTo("1E-323", 0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001d);
669
670
      // "up to 11 numeric characters, with the decimal point counting as a numeric character"
671
      // https://support.microsoft.com/en-us/kb/65903
672
      assertFormatsTo( "12345678911",   12345678911d);
673
      assertFormatsTo( "1.23457E+11",   123456789112d);  // 12th digit of integer -> scientific
674
      assertFormatsTo( "-12345678911", -12345678911d);
675
      assertFormatsTo( "-1.23457E+11", -123456789112d);
676
      assertFormatsTo( "0.1",           0.1);
677
      assertFormatsTo( "0.000000001",   0.000000001);
678
      assertFormatsTo( "1E-10",         0.0000000001);  // 12th digit
679
      assertFormatsTo( "-0.000000001", -0.000000001);
680
      assertFormatsTo( "-1E-10",       -0.0000000001);
681
      assertFormatsTo( "123.4567892",   123.45678919);  // excess decimals are simply rounded away
682
      assertFormatsTo("-123.4567892",  -123.45678919);
683
      assertFormatsTo( "123.4567893",   123.45678925);  // rounding mode is half-up
684
      assertFormatsTo("-123.4567893",  -123.45678925);
685
      assertFormatsTo( "1.23457E+19",   12345650000000000000d);
686
      assertFormatsTo("-1.23457E+19",  -12345650000000000000d);
687
      assertFormatsTo( "1.23457E-19",   0.0000000000000000001234565d);
688
      assertFormatsTo("-1.23457E-19",  -0.0000000000000000001234565d);
689
      assertFormatsTo( "1.000000001",   1.000000001);
690
      assertFormatsTo( "1",             1.0000000001);
691
      assertFormatsTo( "1234.567891",   1234.567891123456789d);
692
      assertFormatsTo( "1234567.891",   1234567.891123456789d);
693
      assertFormatsTo( "12345678912",   12345678911.63456789d);  // integer portion uses all 11 digits
694
      assertFormatsTo( "12345678913",   12345678912.5d);  // half-up here too
695
      assertFormatsTo("-12345678913",  -12345678912.5d);
696
      assertFormatsTo( "1.23457E+11",   123456789112.3456789d);
697
    }
698
699
    private static void assertFormatsTo(String expected, double input) throws IOException {
700
        Workbook wb = new HSSFWorkbook();
701
        try {
702
            Sheet s1 = wb.createSheet();
703
            Row row = s1.createRow(0);
704
            Cell rawValue = row.createCell(0);
705
            rawValue.setCellValue(input);
706
            CellStyle newStyle = wb.createCellStyle();
707
            DataFormat dataFormat = wb.createDataFormat();
708
            newStyle.setDataFormat(dataFormat.getFormat("General"));
709
            String actual = new DataFormatter().formatCellValue(rawValue);
710
            assertEquals(expected, actual);
711
        }
712
        finally {
713
            wb.close();
714
        }
715
    }
663
}
716
}
664
- 

Return to bug 58471