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)
+ );
+ }
}