-
Notifications
You must be signed in to change notification settings - Fork 6.2k
8368001: java/text/Format/NumberFormat/NumberRoundTrip.java timed out #28443
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| /* | ||
| * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved. | ||
| * Copyright (c) 1997, 2025, 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 | ||
|
|
@@ -23,19 +23,24 @@ | |
|
|
||
| /* | ||
| * @test | ||
| * @summary round trip test NumberFormat | ||
| * @bug 4266589 8031145 8164791 8316696 8368001 | ||
| * @summary NumberFormat round trip testing of parsing and formatting. | ||
| * This test checks 4 factory instances per locale against ~20 numeric inputs. | ||
| * Samples ~1/4 of the available locales from NumberFormat SPI. | ||
| * @key randomness | ||
| * @run junit NumberRoundTrip | ||
| */ | ||
|
|
||
| import java.text.DecimalFormat; | ||
| import java.text.NumberFormat; | ||
| import java.text.ParseException; | ||
| import java.util.Locale; | ||
| import java.util.Arrays; | ||
| import java.util.List; | ||
| import java.util.stream.Stream; | ||
|
|
||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| import static org.junit.jupiter.api.Assertions.fail; | ||
| import org.junit.jupiter.api.Assertions; | ||
| import org.junit.jupiter.params.ParameterizedTest; | ||
| import org.junit.jupiter.params.provider.Arguments; | ||
| import org.junit.jupiter.params.provider.MethodSource; | ||
|
|
||
| /** | ||
| * This class tests the round-trip behavior of NumberFormat, DecimalFormat, and DigitList. | ||
|
|
@@ -44,187 +49,105 @@ | |
| * Two tests are applied: String preservation, and numeric preservation. String | ||
| * preservation is exact; numeric preservation is not. However, numeric preservation | ||
| * should extend to the few least-significant bits. | ||
| * //bug472 | ||
| */ | ||
| public class NumberRoundTrip { | ||
| static final boolean STRING_COMPARE = true; | ||
| static final boolean EXACT_NUMERIC_COMPARE = false; | ||
| static final double MAX_ERROR = 1e-14; | ||
| static double max_numeric_error = 0; | ||
| static double min_numeric_error = 1; | ||
|
|
||
| String localeName, formatName; | ||
|
|
||
| @Test | ||
| public void TestNumberFormatRoundTrip() { | ||
| System.out.println("Default Locale"); | ||
| localeName = "Default Locale"; | ||
| formatName = "getInstance"; | ||
| doTest(NumberFormat.getInstance()); | ||
| formatName = "getNumberInstance"; | ||
| doTest(NumberFormat.getNumberInstance()); | ||
| formatName = "getCurrencyInstance"; | ||
| doTest(NumberFormat.getCurrencyInstance()); | ||
| formatName = "getPercentInstance"; | ||
| doTest(NumberFormat.getPercentInstance()); | ||
|
|
||
| Locale[] loc = NumberFormat.getAvailableLocales(); | ||
| for (int i=0; i<loc.length; ++i) { | ||
| System.out.println(loc[i].getDisplayName()); | ||
| localeName = loc[i].toString(); | ||
| formatName = "getInstance"; | ||
| doTest(NumberFormat.getInstance(loc[i])); | ||
| formatName = "getNumberInstance"; | ||
| doTest(NumberFormat.getNumberInstance(loc[i])); | ||
| formatName = "getCurrencyInstance"; | ||
| doTest(NumberFormat.getCurrencyInstance(loc[i])); | ||
| formatName = "getPercentInstance"; | ||
| doTest(NumberFormat.getPercentInstance(loc[i])); | ||
| } | ||
|
|
||
| System.out.println("Numeric error " + | ||
| min_numeric_error + " to " + | ||
| max_numeric_error); | ||
| @ParameterizedTest | ||
| @MethodSource | ||
| void testNumberFormatRoundTrip(NumberFormat fmt) { | ||
| fmt.setMaximumFractionDigits(Integer.MAX_VALUE); | ||
| Stream.concat(numbers.stream(), randomNumbers()) | ||
| .forEach(num -> test(fmt, num)); | ||
| } | ||
|
|
||
| public void doTest(NumberFormat fmt) { | ||
| doTest(fmt, Double.NaN); | ||
| doTest(fmt, Double.POSITIVE_INFINITY); | ||
| doTest(fmt, Double.NEGATIVE_INFINITY); | ||
|
|
||
| doTest(fmt, 500); | ||
| doTest(fmt, 0); | ||
| doTest(fmt, 5555555555555555L); | ||
| doTest(fmt, 55555555555555555L); | ||
| doTest(fmt, 9223372036854775807L); | ||
| doTest(fmt, 9223372036854775808.0); | ||
| doTest(fmt, -9223372036854775808L); | ||
| doTest(fmt, -9223372036854775809.0); | ||
|
|
||
| for (int i=0; i<2; ++i) { | ||
| doTest(fmt, randomDouble(1)); | ||
| doTest(fmt, randomDouble(10000)); | ||
| doTest(fmt, Math.floor(randomDouble(10000))); | ||
| doTest(fmt, randomDouble(1e50)); | ||
| doTest(fmt, randomDouble(1e-50)); | ||
| doTest(fmt, randomDouble(1e100)); | ||
| // The use of double d such that isInfinite(100d) causes the | ||
| // numeric test to fail with percent formats (bug 4266589). | ||
| // Largest double s.t. 100d < Inf: d=1.7976931348623156E306 | ||
| doTest(fmt, randomDouble(1e306)); | ||
| doTest(fmt, randomDouble(1e-323)); | ||
| doTest(fmt, randomDouble(1e-100)); | ||
| private void test(NumberFormat fmt, Number num) { | ||
| String originalFormatted = fmt.format(num); | ||
| Number parsedNum = Assertions.assertDoesNotThrow(() -> fmt.parse(originalFormatted), | ||
| "Failed parse(format(%s))".formatted(num)); | ||
| String parsedFormatted = fmt.format(parsedNum); | ||
| var equal = originalFormatted.equals(parsedFormatted); | ||
| // Try BigDecimal parsing, if not equal | ||
| if (!equal) { | ||
| var df = Assertions.assertInstanceOf(DecimalFormat.class, fmt); | ||
| df.setParseBigDecimal(true); | ||
| parsedNum = Assertions.assertDoesNotThrow(() -> fmt.parse(originalFormatted), | ||
| "Failed BigDecimal parse(format(%s))".formatted(num)); | ||
| parsedFormatted = fmt.format(parsedNum); | ||
| df.setParseBigDecimal(false); | ||
| Assertions.assertEquals(originalFormatted, parsedFormatted, | ||
| "Failed to round-trip format(parse(format(%s)))".formatted(num)); | ||
| } | ||
| // Numeric mismatch to the amount of 1e-14 is tolerable | ||
| var error = proportionalError(num, parsedNum); | ||
| Assertions.assertFalse(error > 1e-14, | ||
| "Round tripping %s caused numeric error: %s".formatted(num, error)); | ||
| } | ||
|
|
||
| /** | ||
| * Return a random value from -range..+range. | ||
| */ | ||
| public double randomDouble(double range) { | ||
| double a = Math.random(); | ||
| return (2.0 * range * a) - range; | ||
| // Regular, number, currency, and percent instance per locale | ||
| private static Stream<Arguments> testNumberFormatRoundTrip() { | ||
| return Stream.concat( | ||
| // Default Locale | ||
| Stream.of( | ||
| Arguments.of(NumberFormat.getInstance()), | ||
| Arguments.of(NumberFormat.getNumberInstance()), | ||
| Arguments.of(NumberFormat.getCurrencyInstance()), | ||
| Arguments.of(NumberFormat.getPercentInstance())), | ||
| // ~1000 locales returned from provider. | ||
| // Too expensive to test all locales, so sample a reasonable amount | ||
| Arrays.stream(NumberFormat.getAvailableLocales()) | ||
| .filter(_ -> Math.random() < .25) | ||
| .flatMap(loc -> Stream.of( | ||
| Arguments.of(NumberFormat.getInstance(loc)), | ||
| Arguments.of(NumberFormat.getNumberInstance(loc)), | ||
| Arguments.of(NumberFormat.getCurrencyInstance(loc)), | ||
| Arguments.of(NumberFormat.getPercentInstance(loc))) | ||
| ) | ||
| ); | ||
| } | ||
|
|
||
| public void doTest(NumberFormat fmt, double value) { | ||
| doTest(fmt, Double.valueOf(value)); | ||
| // Fixed set of numbers to test each locale against | ||
| private static final List<Number> numbers = List.of( | ||
| Double.NaN, | ||
| Double.POSITIVE_INFINITY, | ||
| Double.NEGATIVE_INFINITY, | ||
| 500, | ||
| 0, | ||
| 5555555555555555L, | ||
| 55555555555555555L, | ||
| 9223372036854775807L, | ||
| 9223372036854775808.0, | ||
| -9223372036854775808L, | ||
| -9223372036854775809.0 | ||
| ); | ||
|
|
||
| // Compute fresh batch of random numbers per locale | ||
| private Stream<Number> randomNumbers() { | ||
| return Stream.of( | ||
| randomDouble(1), | ||
| randomDouble(10000), | ||
| Math.floor(randomDouble(10000)), | ||
| randomDouble(1e50), | ||
| randomDouble(1e-50), | ||
| randomDouble(1e100), | ||
| // The use of double d such that isInfinite(100d) causes the | ||
| // numeric test to fail with percent formats (bug 4266589). | ||
| // Largest double s.t. 100d < Inf: d=1.7976931348623156E306 | ||
| randomDouble(1e306), | ||
| randomDouble(1e-323), | ||
| randomDouble(1e-100) | ||
| ); | ||
| } | ||
|
|
||
| public void doTest(NumberFormat fmt, long value) { | ||
| doTest(fmt, Long.valueOf(value)); | ||
| // Return a random value from -range..+range. | ||
| private static double randomDouble(double range) { | ||
| double a = Math.random(); | ||
|
||
| return (2.0 * range * a) - range; | ||
| } | ||
|
|
||
| static double proportionalError(Number a, Number b) { | ||
| private static double proportionalError(Number a, Number b) { | ||
| double aa = a.doubleValue(), bb = b.doubleValue(); | ||
| double error = aa - bb; | ||
| if (aa != 0 && bb != 0) error /= aa; | ||
| return Math.abs(error); | ||
| } | ||
|
|
||
| public void doTest(NumberFormat fmt, Number value) { | ||
| fmt.setMaximumFractionDigits(Integer.MAX_VALUE); | ||
| String s = fmt.format(value), s2 = null; | ||
| Number n = null; | ||
| String err = ""; | ||
| try { | ||
| System.out.println(" " + value + " F> " + escape(s)); | ||
| n = fmt.parse(s); | ||
| System.out.println(" " + escape(s) + " P> " + n); | ||
| s2 = fmt.format(n); | ||
| System.out.println(" " + n + " F> " + escape(s2)); | ||
|
|
||
| if (STRING_COMPARE) { | ||
| if (!s.equals(s2)) { | ||
| if (fmt instanceof DecimalFormat) { | ||
| System.out.println("Text mismatch: expected: " + s + ", got: " + s2 + " --- Try BigDecimal parsing."); | ||
| ((DecimalFormat)fmt).setParseBigDecimal(true); | ||
| n = fmt.parse(s); | ||
| System.out.println(" " + escape(s) + " P> " + n); | ||
| s2 = fmt.format(n); | ||
| System.out.println(" " + n + " F> " + escape(s2)); | ||
| ((DecimalFormat)fmt).setParseBigDecimal(false); | ||
|
|
||
| if (!s.equals(s2)) { | ||
| err = "STRING ERROR(DecimalFormat): "; | ||
| } | ||
| } else { | ||
| err = "STRING ERROR(NumberFormat): "; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if (EXACT_NUMERIC_COMPARE) { | ||
| if (value.doubleValue() != n.doubleValue()) { | ||
| err += "NUMERIC ERROR: "; | ||
| } | ||
| } else { | ||
| // Compute proportional error | ||
| double error = proportionalError(value, n); | ||
|
|
||
| if (error > MAX_ERROR) { | ||
| err += "NUMERIC ERROR " + error + ": "; | ||
| } | ||
|
|
||
| if (error > max_numeric_error) max_numeric_error = error; | ||
| if (error < min_numeric_error) min_numeric_error = error; | ||
| } | ||
|
|
||
| String message = value + typeOf(value) + " F> " + | ||
| escape(s) + " P> " + | ||
| n + typeOf(n) + " F> " + | ||
| escape(s2); | ||
| if (err.length() > 0) { | ||
| fail("*** " + err + " with " + | ||
| formatName + " in " + localeName + | ||
| " " + message); | ||
| } else { | ||
| System.out.println(message); | ||
| } | ||
| } catch (ParseException e) { | ||
| fail("*** " + e.toString() + " with " + | ||
| formatName + " in " + localeName); | ||
| } | ||
| } | ||
|
|
||
| static String typeOf(Number n) { | ||
| if (n instanceof Long) return " Long"; | ||
| if (n instanceof Double) return " Double"; | ||
| return " Number"; | ||
| } | ||
|
|
||
| static String escape(String s) { | ||
| StringBuffer buf = new StringBuffer(); | ||
| for (int i=0; i<s.length(); ++i) { | ||
| char c = s.charAt(i); | ||
| if (c < (char)0xFF) { | ||
| buf.append(c); | ||
| } else { | ||
| buf.append("\\U"); | ||
| buf.append(Integer.toHexString((c & 0xF000) >> 12)); | ||
| buf.append(Integer.toHexString((c & 0x0F00) >> 8)); | ||
| buf.append(Integer.toHexString((c & 0x00F0) >> 4)); | ||
| buf.append(Integer.toHexString(c & 0x000F)); | ||
| } | ||
| } | ||
| return buf.toString(); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: it is not using SPI