This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-text.git


The following commit(s) were added to refs/heads/master by this push:
     new 4331f2ef Update DoubleFormat to state it is based on Double.toString. 
(#467)
4331f2ef is described below

commit 4331f2ef85801e4aba4a3999bc39ba33d6b68b0c
Author: Alex Herbert <[email protected]>
AuthorDate: Fri Oct 20 11:20:20 2023 +0100

    Update DoubleFormat to state it is based on Double.toString. (#467)
    
    This changes the tests to fix some incompatibility between the reference
    DecimalFormat class and DoubleFormat when:
    
    - The output string has a trailing .0 due to rounding (which may be
    omitted by DecimalFormat).
    - The Double.toString output has fewer chars than the full precision
    output from DecimalFormat.
---
 .../apache/commons/text/numbers/DoubleFormat.java  | 10 ++++
 .../commons/text/numbers/DoubleFormatTest.java     | 64 +++++++++++++++++++++-
 2 files changed, 71 insertions(+), 3 deletions(-)

diff --git a/src/main/java/org/apache/commons/text/numbers/DoubleFormat.java 
b/src/main/java/org/apache/commons/text/numbers/DoubleFormat.java
index 05cae505..cd42017b 100644
--- a/src/main/java/org/apache/commons/text/numbers/DoubleFormat.java
+++ b/src/main/java/org/apache/commons/text/numbers/DoubleFormat.java
@@ -39,6 +39,16 @@ import java.util.function.Function;
  * much easier to work with in multi-threaded environments. They also provide 
performance
  * comparable to, and in many cases faster than, {@code DecimalFormat}.
  * </p>
+ * <p>
+ * It should be noted that the output {@code String} is created by formatting 
the output of
+ * {@link Double#toString()}. This limits the output precision to the 
precision required
+ * to exactly represent the input {@code double} and is dependent on the JDK 
implementation
+ * of {@link Double#toString()}. A number formatted with the maximum
+ * precision should be parsed to the same input {@code double}. This 
implementation
+ * cannot extend the {@code String} to the required length to represent the 
exact decimal
+ * value of the {@code double} as per
+ * {@link java.math.BigDecimal#toString() BigDecimal#toString()}.
+ * </p>
  * <p><strong>Examples</strong></p>
  * <pre>
  * // construct a formatter equivalent to Double.toString()
diff --git 
a/src/test/java/org/apache/commons/text/numbers/DoubleFormatTest.java 
b/src/test/java/org/apache/commons/text/numbers/DoubleFormatTest.java
index 0ebc12d0..e05cec3b 100644
--- a/src/test/java/org/apache/commons/text/numbers/DoubleFormatTest.java
+++ b/src/test/java/org/apache/commons/text/numbers/DoubleFormatTest.java
@@ -22,9 +22,13 @@ import java.util.Locale;
 import java.util.Random;
 import java.util.function.DoubleFunction;
 import java.util.function.Function;
+import java.util.stream.Stream;
 
 import org.junit.jupiter.api.Assertions;
 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;
 
 public class DoubleFormatTest {
 
@@ -37,8 +41,28 @@ public class DoubleFormatTest {
         final String dfStr = trimFormatChars(df.format(d));
         final String fmtStr = trimFormatChars(fmt.apply(d));
 
-        Assertions.assertEquals(dfStr, fmtStr,
+        try {
+            Assertions.assertEquals(dfStr, fmtStr,
                 () -> "Unexpected output for locale [" + loc.toLanguageTag() + 
"] and double value " + d);
+        } catch (AssertionError e) {
+            // Note:
+            // The DecimalFormat may omit the fraction component if it is zero
+            // when using the ENGINEERING format "##0.0##E0".
+            // e.g. new DecimalFormat("##0.0##E0").format(1.1299999e-4) => 
113E-6.
+            // The DoubleFormat class either always includes the zero or 
removes it
+            // with the setting includeFractionPlaceholder(false).
+            // Since we expect this mismatch we can remove the decimal point 
followed
+            // by a zero from the DoubleFormat output.
+            // This effectively checks: abcExyz == abc.0Exyz
+            final DecimalFormatSymbols dfs = new DecimalFormatSymbols(loc);
+            final char decimalSeparator = dfs.getDecimalSeparator();
+            final char zeroDigit = dfs.getZeroDigit();
+            final String updated = fmtStr.replace(new String(new char[] 
{decimalSeparator, zeroDigit}), "");
+            if (dfStr.equals(updated)) {
+                return;
+            }
+            throw e;
+        }
     }
 
     private static void checkDefaultFormatSpecial(final DoubleFunction<String> 
fmt) {
@@ -113,9 +137,18 @@ public class DoubleFormatTest {
         assertLocalizedFormatsAreEqual(Math.PI, df, fmt, loc);
         assertLocalizedFormatsAreEqual(Math.E, df, fmt, loc);
 
+        // Locales are tested using:
+        // DecimalFormat   DoubleFormat
+        // ##0.0##E0     : ENGINEERING  maPrecision=6
+        // 0.0##         : PLAIN        minDecimalExponent(-3)
+        // #,##0.0##     : PLAIN        minDecimalExponent(-3)
+        // 0.0##E0       : SCIENTIFIC   maPrecision=4
+        // The data should not test full precision (17 digits) of the PLAIN 
format.
+        // Set the exponent range to create decimals with exponents of 
approximately
+        // 10^7 to 10^-7: log2(1e7) = 23.25.
         final Random rnd = new Random(12L);
-        final int minExp = -100;
-        final int maxExp = 100;
+        final int minExp = -24;
+        final int maxExp = 24;
         final int cnt = 1000;
         for (int i = 0; i < cnt; ++i) {
             assertLocalizedFormatsAreEqual(randomDouble(minExp, maxExp, rnd), 
df, fmt, loc);
@@ -596,4 +629,29 @@ public class DoubleFormatTest {
                 .formatSymbols(DecimalFormatSymbols.getInstance(loc))
                 .build());
     }
+
+    /**
+     * Test formatting at the maximum precision. The formatting is based on 
the output
+     * of {@link Double#toString()}. If cannot create an extended precision 
text
+     * representation and is limited to 17 significant digits. This test 
verifies that
+     * formatting does not lose information that would be required to recreate 
the
+     * same double value.
+     */
+    @ParameterizedTest
+    @MethodSource
+    void testMaximumPrecision(DoubleFunction<String> fmt, double value) {
+        final String s = fmt.apply(value);
+        final double d = Double.parseDouble(s);
+        Assertions.assertEquals(value, d, () -> value + " formatted as " + s);
+    }
+
+    static Stream<Arguments> testMaximumPrecision() {
+        return Stream.of(
+            // Example of different Double.toString representations across JDKs
+            // JDK 17: -9.3540047119774374E17
+            // JDK 21: -9.354004711977437E17
+            Arguments.of(DoubleFormat.PLAIN.builder().build(), 
-9.3540047119774374E17),
+            Arguments.of(DoubleFormat.SCIENTIFIC.builder().build(), 
-9.3540047119774374E17)
+        );
+    }
 }

Reply via email to