| /* | |
| * Copyright (c) 2003, 2024, 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. | |
| * | |
| * 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 4838107 8008577 8174269 | |
| * @summary Confirm that DecimalFormat can format a number with a negative | |
| * exponent number correctly. Tests also involve using a DecimalFormat | |
| * with a custom pattern or a custom minus sign. | |
| * @run junit Bug4838107 | |
| */ | |
| import java.math.BigDecimal; | |
| import java.math.BigInteger; | |
| import java.text.DecimalFormat; | |
| import java.text.DecimalFormatSymbols; | |
| import java.util.Locale; | |
| import java.util.stream.Stream; | |
| import org.junit.jupiter.api.AfterAll; | |
| import org.junit.jupiter.api.BeforeAll; | |
| 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; | |
| import static org.junit.jupiter.api.Assertions.fail; | |
| /* | |
| * This bug is about exponential formatting. But I added test cases for: | |
| * - Double and BigDecimal numbers which don't have exponent parts. | |
| * - Long and BigInteger numbers which don't support exponential | |
| * notation. | |
| * because there are few test cases for suffix and prefix. | |
| * And also, I added test cases to guarantee further formatting and | |
| * parsing using the same DecimalFormat instance will not change the | |
| * Number's value anymore. | |
| */ | |
| public class Bug4838107 { | |
| // Save JVM default Locale | |
| private static final Locale savedLocale = Locale.getDefault(); | |
| // Set JVM default Locale to US | |
| @BeforeAll | |
| static void init() { | |
| Locale.setDefault(Locale.US); | |
| } | |
| // Restore the original JVM default locale | |
| @AfterAll | |
| static void tearDown() { | |
| Locale.setDefault(savedLocale); | |
| } | |
| // Check that negative exponent number recognized for doubles | |
| @ParameterizedTest | |
| @MethodSource("doubles") | |
| public void doubleTest(Number num, String str, DecimalFormat df) { | |
| test(num, str, df); | |
| } | |
| // Provides a double to be formatted, which is compared to the expected String. | |
| // Additionally, provides a DecimalFormat to do the formatting (can have a custom | |
| // pattern and minus sign). Given in the form (double, String, DecimalFormat). | |
| private static Stream<Arguments> doubles() { | |
| DecimalFormat defaultDf = new DecimalFormat(); | |
| DecimalFormat customDf1 = getDecimalFormat("<P>#.###E00<S>", 'm'); | |
| DecimalFormat customDf2 = getDecimalFormat("<P>#.###E00<S>;#.###E00", 'm'); | |
| DecimalFormat customDf3 = getDecimalFormat("#.###E00;<P>#.###E00<S>", 'm'); | |
| DecimalFormat customDf4 = getDecimalFormat("<P>#.###E00<S>;<p>-#.###E00<s>", 'm'); | |
| return Stream.of( | |
| // Test with default pattern | |
| Arguments.of(1234D, "1,234", defaultDf), | |
| Arguments.of(0.1234, "0.123", defaultDf), // rounded | |
| Arguments.of(-1234D, "-1,234", defaultDf), | |
| Arguments.of(-0.1234, "-0.123", defaultDf), // rounded | |
| Arguments.of(Double.POSITIVE_INFINITY, "\u221e", defaultDf), | |
| Arguments.of(Double.NEGATIVE_INFINITY, "-\u221e", defaultDf), | |
| Arguments.of(Double.NaN, "NaN", defaultDf), // without prefix and suffix | |
| Arguments.of(0.0, "0", defaultDf), | |
| Arguments.of(-0.0, "-0", defaultDf), // with the minus sign | |
| // Test with a pattern and the minus sign | |
| Arguments.of(1234D, "<P>1.234E03<S>", customDf1), | |
| Arguments.of(0.1234, "<P>1.234Em01<S>", customDf1), | |
| Arguments.of(-1234D, "m<P>1.234E03<S>", customDf1), | |
| Arguments.of(-0.1234, "m<P>1.234Em01<S>", customDf1), | |
| Arguments.of(1234D, "<P>1.234E03<S>", customDf2), | |
| Arguments.of(0.1234, "<P>1.234Em01<S>", customDf2), | |
| Arguments.of(-1234D, "1.234E03", customDf2), | |
| Arguments.of(-0.1234, "1.234Em01", customDf2), | |
| Arguments.of(1234D, "1.234E03", customDf3), | |
| Arguments.of(0.1234, "1.234Em01", customDf3), | |
| Arguments.of(-1234D, "<P>1.234E03<S>", customDf3), | |
| Arguments.of(-0.1234, "<P>1.234Em01<S>", customDf3), | |
| Arguments.of(1234D, "<P>1.234E03<S>", customDf4), | |
| Arguments.of(0.1234, "<P>1.234Em01<S>", customDf4), | |
| Arguments.of(-1234D, "<p>m1.234E03<s>", customDf4), | |
| Arguments.of(-0.1234, "<p>m1.234Em01<s>", customDf4), | |
| Arguments.of(Double.POSITIVE_INFINITY, "<P>\u221e<S>", customDf4), | |
| Arguments.of(Double.NEGATIVE_INFINITY, "<p>m\u221e<s>", customDf4), | |
| Arguments.of(Double.NaN, "NaN", customDf4), // without prefix and suffix | |
| Arguments.of(0.0, "<P>0E00<S>", customDf4), | |
| Arguments.of(-0.0, "<p>m0E00<s>", customDf4) // with the minus sign | |
| ); | |
| } | |
| // Check that negative exponent number recognized for longs | |
| @ParameterizedTest | |
| @MethodSource("longs") | |
| public void longTest(Number num, String str, DecimalFormat df) { | |
| test(num, str, df); | |
| } | |
| // Same as doubles() data provider, but with long values | |
| // Given in the form (long, String, DecimalFormat). | |
| private static Stream<Arguments> longs() { | |
| DecimalFormat defaultDf = new DecimalFormat(); | |
| DecimalFormat customDf = getDecimalFormat( | |
| "<P>#,###<S>;<p>-#,###<s>", 'm'); | |
| return Stream.of( | |
| // Test with default pattern | |
| Arguments.of(123456789L, "123,456,789", defaultDf), | |
| Arguments.of(-123456789L, "-123,456,789", defaultDf), | |
| Arguments.of(0L, "0", defaultDf), | |
| Arguments.of(-0L, "0", defaultDf), | |
| // Test with a pattern and the minus sign | |
| Arguments.of(123456789L, "<P>123,456,789<S>", customDf), | |
| Arguments.of(-123456789L, "<p>m123,456,789<s>", customDf), | |
| Arguments.of(0L, "<P>0<S>", customDf), | |
| Arguments.of(-0L, "<P>0<S>", customDf) | |
| ); | |
| } | |
| // Check that negative exponent number recognized for bigDecimals | |
| @ParameterizedTest | |
| @MethodSource("bigDecimals") | |
| public void bigDecimalTest(Number num, String str, DecimalFormat df) { | |
| test(num, str, df); | |
| } | |
| // Same as doubles() data provider, but with BigDecimal values | |
| // Given in the form (BigDecimal, String, DecimalFormat). | |
| private static Stream<Arguments> bigDecimals() { | |
| DecimalFormat defaultDf = new DecimalFormat(); | |
| DecimalFormat customDf = getDecimalFormat( | |
| "<P>#.####################E00<S>;<p>-#.####################E00<s>", 'm'); | |
| return Stream.of( | |
| // Test with default pattern | |
| Arguments.of(new BigDecimal("123456789012345678901234567890"), | |
| "123,456,789,012,345,678,901,234,567,890", defaultDf), | |
| Arguments.of(new BigDecimal("0.000000000123456789012345678901234567890"), | |
| "0", defaultDf), | |
| Arguments.of(new BigDecimal("-123456789012345678901234567890"), | |
| "-123,456,789,012,345,678,901,234,567,890", defaultDf), | |
| Arguments.of(new BigDecimal("-0.000000000123456789012345678901234567890"), | |
| "-0", defaultDf), | |
| Arguments.of(new BigDecimal("0"), "0", defaultDf), | |
| Arguments.of(new BigDecimal("-0"), "0", defaultDf), | |
| // Test with a pattern and the minus sign | |
| Arguments.of(new BigDecimal("123456789012345678901234567890"), | |
| "<P>1.23456789012345678901E29<S>", customDf), | |
| Arguments.of(new BigDecimal("0.000000000123456789012345678901234567890"), | |
| "<P>1.23456789012345678901Em10<S>", customDf), | |
| Arguments.of(new BigDecimal("-123456789012345678901234567890"), | |
| "<p>m1.23456789012345678901E29<s>", customDf), | |
| Arguments.of(new BigDecimal("-0.000000000123456789012345678901234567890"), | |
| "<p>m1.23456789012345678901Em10<s>", customDf), | |
| Arguments.of(new BigDecimal("0"), "<P>0E00<S>", customDf), | |
| Arguments.of(new BigDecimal("-0"), "<P>0E00<S>", customDf) | |
| ); | |
| } | |
| // Check that negative exponent number recognized for bigIntegers | |
| @ParameterizedTest | |
| @MethodSource("bigIntegers") | |
| public void bigIntegerTest(Number num, String str, DecimalFormat df) { | |
| test(num, str, df); | |
| } | |
| // Same as doubles() data provider, but with BigInteger values | |
| // Given in the form (BigInteger, String, DecimalFormat). | |
| private static Stream<Arguments> bigIntegers() { | |
| DecimalFormat defaultDf = new DecimalFormat(); | |
| DecimalFormat customDf = getDecimalFormat( | |
| "<P>#,###<S>;<p>-#,###<s>", 'm'); | |
| return Stream.of( | |
| // Test with default pattern | |
| Arguments.of(new BigInteger("123456789012345678901234567890"), | |
| "123,456,789,012,345,678,901,234,567,890", defaultDf), | |
| Arguments.of(new BigInteger("-123456789012345678901234567890"), | |
| "-123,456,789,012,345,678,901,234,567,890", defaultDf), | |
| Arguments.of(new BigInteger("0"), "0", defaultDf), | |
| Arguments.of(new BigInteger("-0"), "0", defaultDf), | |
| // Test with a pattern and the minus sign | |
| Arguments.of(new BigInteger("123456789012345678901234567890"), | |
| "<P>123,456,789,012,345,678,901,234,567,890<S>", customDf), | |
| Arguments.of(new BigInteger("-123456789012345678901234567890"), | |
| "<p>m123,456,789,012,345,678,901,234,567,890<s>", customDf), | |
| Arguments.of(new BigInteger("0"), "<P>0<S>", customDf), | |
| Arguments.of(new BigInteger("-0"), "<P>0<S>", customDf) | |
| ); | |
| } | |
| // Check that the formatted value is correct and also check that | |
| // it can be round-tripped via parse() and format() | |
| private static void test(Number num, String str, DecimalFormat df) { | |
| String formatted = df.format(num); | |
| assertEquals(str, formatted, String.format("DecimalFormat format(%s) " + | |
| "Error: number: %s, minus sign: %s", num.getClass().getName(), num, df.getDecimalFormatSymbols().getMinusSign())); | |
| if (num instanceof BigDecimal || num instanceof BigInteger) { | |
| df.setParseBigDecimal(true); | |
| } | |
| testRoundTrip(formatted, str, num, df); | |
| } | |
| // Test that a parsed value can be round-tripped via format() and parse() | |
| private static void testRoundTrip(String formatted, String str, | |
| Number num, DecimalFormat df) { | |
| Number parsed1 = null, parsed2 = null; | |
| try { | |
| parsed1 = df.parse(formatted); | |
| formatted = df.format(parsed1); | |
| parsed2 = df.parse(formatted); | |
| assertEquals(parsed2, parsed1, """ | |
| DecimalFormat round trip parse(%s) error: | |
| original number: %s | |
| parsed number: %s | |
| (%s) | |
| formatted number: %s | |
| re-parsed number: %s | |
| (%s) | |
| minus sign: %s | |
| """.formatted(num.getClass().getName(), str, parsed1, parsed1.getClass().getName(), | |
| formatted, parsed2, parsed2.getClass().getName(), df.getDecimalFormatSymbols().getMinusSign())); | |
| } | |
| catch (Exception e) { | |
| fail(""" | |
| DecimalFormat parse(%s) threw an Exception: %s | |
| original number: %s | |
| parsed number: %s | |
| (%s) | |
| formatted number: %s | |
| re-parsed number: %s | |
| (%s) | |
| minus sign: %s | |
| """.formatted(num.getClass().getName(), e.getMessage(), str, parsed1, parsed1.getClass().getName(), | |
| formatted, parsed2, parsed2.getClass().getName(), df.getDecimalFormatSymbols().getMinusSign())); | |
| } | |
| } | |
| // Set up custom DecimalFormat with DecimalFormatSymbols | |
| private static DecimalFormat getDecimalFormat(String pattern, char minusSign) { | |
| DecimalFormat df = new DecimalFormat(); | |
| DecimalFormatSymbols dfs = df.getDecimalFormatSymbols(); | |
| df.applyPattern(pattern); | |
| dfs.setMinusSign(minusSign); | |
| df.setDecimalFormatSymbols(dfs); | |
| return df; | |
| } | |
| } |