From 46c4da7fddb8103934f2a90b4456a5ce6ed3467c Mon Sep 17 00:00:00 2001 From: Justin Lu Date: Thu, 25 May 2023 21:54:58 +0000 Subject: [PATCH] 8159023: Engineering notation of DecimalFormat does not work as documented Reviewed-by: naoto --- .../classes/java/text/DecimalFormat.java | 49 ++++++-- .../Format/DecimalFormat/MantissaDigits.java | 105 ++++++++++++++++++ 2 files changed, 147 insertions(+), 7 deletions(-) create mode 100644 test/jdk/java/text/Format/DecimalFormat/MantissaDigits.java diff --git a/src/java.base/share/classes/java/text/DecimalFormat.java b/src/java.base/share/classes/java/text/DecimalFormat.java index 717daabffce..3e0f1e3246f 100644 --- a/src/java.base/share/classes/java/text/DecimalFormat.java +++ b/src/java.base/share/classes/java/text/DecimalFormat.java @@ -264,6 +264,16 @@ import sun.util.locale.provider.ResourceBundleBasedAdapter; * formatted using the localized minus sign, not the prefix and suffix * from the pattern. This allows patterns such as {@code "0.###E0 m/s"}. * + *
  • The maximum integer digits is the sum of '0's and '#'s + * prior to the decimal point. The minimum integer digits is the + * sum of the '0's prior to the decimal point. The maximum fraction + * and minimum fraction digits follow the same rules, but apply to the + * digits after the decimal point but before the exponent. For example, the + * following pattern: {@code "#00.0####E0"} would have a minimum number of + * integer digits = 2("00") and a maximum number of integer digits = 3("#00"). It + * would have a minimum number of fraction digits = 1("0") and a maximum number of fraction + * digits= 5("0####"). + * *
  • The minimum and maximum number of integer digits are interpreted * together: * @@ -282,13 +292,38 @@ import sun.util.locale.provider.ResourceBundleBasedAdapter; * {@code "12.3E-4"}. * * - *
  • The number of significant digits in the mantissa is the sum of the - * minimum integer and maximum fraction digits, and is - * unaffected by the maximum integer digits. For example, 12345 formatted with - * {@code "##0.##E0"} is {@code "12.3E3"}. To show all digits, set - * the significant digits count to zero. The number of significant digits + *
  • For a given number, the amount of significant digits in + * the mantissa can be calculated as such + * + *
    + * Mantissa Digits:
    + *         min(max(Minimum Pattern Digits, Original Number Digits), Maximum Pattern Digits)
    + * Minimum pattern Digits:
    + *         Minimum Integer Digits + Minimum Fraction Digits
    + * Maximum pattern Digits:
    + *         Maximum Integer Digits + Maximum Fraction Digits
    + * Original Number Digits:
    + *         The amount of significant digits in the number to be formatted
    + * 
    + * + * This means that generally, a mantissa will have up to the combined maximum integer + * and fraction digits, if the original number itself has enough significant digits. However, + * if there are more minimum pattern digits than significant digits in the original number, + * the mantissa will have significant digits that equals the combined + * minimum integer and fraction digits. The number of significant digits * does not affect parsing. * + *

    It should be noted, that the integer portion of the mantissa will give + * any excess digits to the fraction portion, whether it be for precision or + * for satisfying the total amount of combined minimum digits. + * + *

    This behavior can be observed in the following example, + * {@snippet lang=java : + * DecimalFormat df = new DecimalFormat("#000.000##E0"); + * df.format(12); // returns "12.0000E0" + * df.format(123456789) // returns "1.23456789E8" + * } + * *

  • Exponential patterns may not contain grouping separators. * * @@ -308,8 +343,8 @@ import sun.util.locale.provider.ResourceBundleBasedAdapter; * *

    Special Values

    * - *

    {@code NaN} is formatted as a string, which typically has a single character - * {@code U+FFFD}. This string is determined by the + *

    Not a Number({@code NaN}) is formatted as a string, which typically has a + * single character {@code U+FFFD}. This string is determined by the * {@code DecimalFormatSymbols} object. This is the only value for which * the prefixes and suffixes are not used. * diff --git a/test/jdk/java/text/Format/DecimalFormat/MantissaDigits.java b/test/jdk/java/text/Format/DecimalFormat/MantissaDigits.java new file mode 100644 index 00000000000..9f69196d746 --- /dev/null +++ b/test/jdk/java/text/Format/DecimalFormat/MantissaDigits.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8159023 + * @summary Confirm behavior of mantissa for scientific notation in Decimal Format + * @run junit MantissaDigits + */ + +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.Locale; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class MantissaDigits { + private static final double[] NUMBERS = { + 1.1, 12.1, 123.1, 1234.1, 12345.1, 123456.1, + -1.1, -12.1, -123.1, -1234.1, -12345.1, -123456.1, + 1, 12, 123, 1234, 12345, 123456, 1234567, + -1, -12, -123, -1234, -12345, -123456, -1234567, + 1.1234, 1.1111, 1.412, 222.333, -771.2222 + }; + private static final DecimalFormatSymbols DFS = new DecimalFormatSymbols(Locale.US); + private static final String ERRMSG = "%s formatted with %s gives %s, and " + + "significant digit count was %s, but the formula provided %s%n"; + // Hard coded as 1, since all test patterns only have 1 exponent digit + private static final int EXPONENTDIGITS = 1; + + @ParameterizedTest + @MethodSource("patterns") + public void testMantissaDefinition(String pattern, int minDigits, int maxDigits) { + DecimalFormat df = new DecimalFormat(pattern, DFS); + for (double number : NUMBERS) { + // Count the significant digits in the pre-formatted number + int originalNumDigits = (int) String.valueOf(number).chars() + .filter(Character::isDigit).count(); + + if (wholeNumber(number)) { + // Trailing 0 should not be counted + originalNumDigits--; + } + + // Format the number, then grab the significant + // digits inside the mantissa + String formattedNum = df.format(number); + int mantissaDigits = (int) formattedNum.chars() + .filter(Character::isDigit).count() - EXPONENTDIGITS; + + // Test the new definition of the Mantissa + Integer calculatedDigits = Math + .min(Math.max(minDigits, originalNumDigits), maxDigits); + assertEquals(mantissaDigits, calculatedDigits, String.format(ERRMSG, + number, pattern, formattedNum, mantissaDigits, calculatedDigits)); + } + } + + private static Boolean wholeNumber(double number) { + return (int) number == number; + } + + private static Stream patterns() { + return Stream.of( + Arguments.of("#0.0##E0", 2, 5), + Arguments.of("#00.00##E0", 4, 7), + Arguments.of("#0.000##E0", 4, 7), + Arguments.of("#00.000##E0", 5, 8), + Arguments.of("#000.0##E0", 4, 7), + Arguments.of("#000.00##E0", 5, 8), + Arguments.of("#000.000##E0", 6, 9), + Arguments.of("000.000E0", 6, 6), + Arguments.of("#.##E0", 0, 3), + Arguments.of("######.######E0", 0, 12), + Arguments.of("####00.00######E0", 4, 14) + ); + } +}