TLDR; The Double.toString(double) value is different for
-9.354004711977437E17 on JDK 21 and earlier JDKs:

JDK 21: -9.354004711977437E17
JDK 17: -9.3540047119774374E17

The DoubleFormat class is built upon Double.toString. So the test
fails due to this change.

---

More details:

On JDK 21 Double.toString is dropping the last digit (a 4) as it is
not required to uniquely represent the double from the next double up
or down. Note the javadoc for toString states: "This decimal is
(almost always) the shortest one that rounds to m according to the
round to nearest rounding policy of IEEE 754 floating-point
arithmetic." So this is not the closest decimal representation of the
double, just the closest required for rounding to the double.

I do not think this is a bug. But we should document that the
DoubleFormat class is built upon the Double.toString representation of
a double. This string may not be the closest decimal representation of
the double. Thus final digit errors can be observed when using the
entire character output of Double.toString compared to other
formatting classes such as java.lang.DecimalFormat or the numerical
string representation provided by java.lang.BigDecimal (see examples
below).

Q. How to fix the test?

The DoubleFormatTest is using 1000 random double values with a base-2
exponent of -100 to 100. Then testing against a plain text format of
0.00##. The exponent of 9.35e17 is 59. It is so large there are no
digits after the decimal point. So the test is checking if
DoubleFormat is accurate to 17 decimal digits of precision, which it
is not in this case. The other tests using the random numbers test the
formats:

#,##0.0##
0.0##E0
##0.0##E0

So the scientific and engineering tests are only checking 4 and 6
decimal digits. But the plain formats are checking up to 17 digits due
to the large exponent of the random numbers. This does not seem very
fair.

Fix 1: A simple fix would be to reduce the exponent range for random
numbers so that the plain numbers should always have at least 4 digits
after the decimal point.

However, changing the test like this would reduce its power. It would
not have alerted us to this failure.

Fix 2: An alternative would be to change the test assertion to perform
the current check, and if it fails perform a test that all digits from
Double.toString are present in the formatted string in the same order
(i.e. the class has used the entire output from Double.toString and so
is at its limit). This is non-trivial as the double value is tested in
all the installed locales which may change the digits and other
formatting characters. The assertion would have to create a reverse
parsing method based on the locale.

Note: I did try using the DecimalFormat used to specify formatting the
expected string to parse the string with DecimalFormat.parse. It
worked on the original root locale but it failed on locale "he"; this
may be another bug in DoubleFormat since the locale can parse its own
output:

jshell> var df = new java.text.DecimalFormat("0.0##",
java.text.DecimalFormatSymbols.getInstance(Locale.forLanguageTag("he")))
jshell> x
x ==> -9.3540047119774374E17
jshell> df.parse(df.format(x)).doubleValue()
$32 ==> -9.3540047119774374E17

Fix 3: Implement fix 1, plus add a test for full length precision
using only the root locale. This can quickly test the output is at the
limit by checking the string creates the original input double using
Double.parseDouble, i.e. the DoubleFormat has captured the entire
information required to uniquely recreate the original double.

Alex

---

Here is the failure:

[ERROR] Failures:
[ERROR]   
DoubleFormatTest.testPlain_localeFormatComparison:491->checkLocalizedFormats:127->checkLocalizedFormat:121->assertLocalized
FormatsAreEqual:40 Unexpected output for locale [und] and double value
-9.354004711977437E17 ==> expected: <-935400471197743740.0> bu
t was: <-935400471197743700.0>

The Double.toString value is the minimum string required to uniquely
identify the double value; this is the string required to round trip
the number to a string and back via Double.parseDouble(String).
However, it may not be the closest decimal representation of the
value.

In the failed test the double value is -9.354004711977437E17 which can
be interpreted differently. I obtained the value that failed using
Double.doubleToLongBits. This has different representations depending
on how it is output:

jshell
|  Welcome to JShell -- Version 17.0.6
|  For an introduction type: /help intro

jshell> double x = Double.longBitsToDouble(-4347673023486037259L)
x ==> -9.3540047119774374E17

jshell> new BigDecimal(x)
$2 ==> -935400471197743744

// Using the DecimalFormat from the failing test:
jshell> new java.text.DecimalFormat("0.0##",
java.text.DecimalFormatSymbols.getInstance(Locale.ROOT)).format(x)
$3 ==> "-935400471197743740.0"

jshell> Math.nextUp(x)
$4 ==> -9.3540047119774362E17

jshell> Math.nextDown(x)
$5 ==> -9.3540047119774387E17


Note the difference for JDK 21:

jshell
|  Welcome to JShell -- Version 21
|  For an introduction type: /help intro

jshell> double x = Double.longBitsToDouble(-4347673023486037259L)
x ==> -9.354004711977437E17

jshell> Double.toString(x)
$3 ==> "-9.354004711977437E17"

jshell> Math.nextUp(x)
$4 ==> -9.354004711977436E17

jshell> Math.nextDown(x)
$5 ==> -9.354004711977439E17






On Sun, 15 Oct 2023 at 18:57, Gary Gregory <garydgreg...@gmail.com> wrote:
>
> Hi all,
>
> Can someone explain the 1 test failure on Java 21 in DoubleFornatTest? A
> one digit difference?
>
> Gary

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@commons.apache.org
For additional commands, e-mail: dev-h...@commons.apache.org

Reply via email to