View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.text.numbers;
18  
19  import java.text.DecimalFormat;
20  import java.text.DecimalFormatSymbols;
21  import java.util.Locale;
22  import java.util.Random;
23  import java.util.function.DoubleFunction;
24  import java.util.function.Function;
25  import java.util.stream.Stream;
26  
27  import org.junit.jupiter.api.Assertions;
28  import org.junit.jupiter.api.Test;
29  import org.junit.jupiter.params.ParameterizedTest;
30  import org.junit.jupiter.params.provider.Arguments;
31  import org.junit.jupiter.params.provider.MethodSource;
32  
33  public class DoubleFormatTest {
34  
35      private static void assertLocalizedFormatsAreEqual(final double d, final DecimalFormat df,
36              final DoubleFunction<String> fmt, final Locale loc) {
37          // NOTE: Perform the string comparison only on non-format characters. This is required because
38          // JDK 16 adds directionality characters to strings for certain locales, such as Arabic, whereas
39          // previous JDKs do not. We will match the behavior of the previous versions here and ignore formatting
40          // for test purposes.
41          final String dfStr = trimFormatChars(df.format(d));
42          final String fmtStr = trimFormatChars(fmt.apply(d));
43  
44          try {
45              Assertions.assertEquals(dfStr, fmtStr,
46                  () -> "Unexpected output for locale [" + loc.toLanguageTag() + "] and double value " + d);
47          } catch (final AssertionError e) {
48              // Note:
49              // The DecimalFormat may omit the fraction component if it is zero
50              // when using the ENGINEERING format "##0.0##E0".
51              // e.g. new DecimalFormat("##0.0##E0").format(1.1299999e-4) => 113E-6.
52              // The DoubleFormat class either always includes the zero or removes it
53              // with the setting includeFractionPlaceholder(false).
54              // Since we expect this mismatch we can remove the decimal point followed
55              // by a zero from the DoubleFormat output.
56              // This effectively checks: abcExyz == abc.0Exyz
57              final DecimalFormatSymbols dfs = new DecimalFormatSymbols(loc);
58              final char decimalSeparator = dfs.getDecimalSeparator();
59              final char zeroDigit = dfs.getZeroDigit();
60              final String updated = fmtStr.replace(new String(new char[] {decimalSeparator, zeroDigit}), "");
61              if (dfStr.equals(updated)) {
62                  return;
63              }
64              throw e;
65          }
66      }
67  
68      private static void checkDefaultFormatSpecial(final DoubleFunction<String> fmt) {
69          checkFormat(fmt, 0.0, "0.0");
70          checkFormat(fmt, -0.0, "-0.0");
71          checkFormat(fmt, Double.NaN, "NaN");
72          checkFormat(fmt, Double.POSITIVE_INFINITY, "Infinity");
73          checkFormat(fmt, Double.NEGATIVE_INFINITY, "-Infinity");
74      }
75  
76      private static void checkFormat(final DoubleFunction<String> fmt, final double d, final String str) {
77          Assertions.assertEquals(str, fmt.apply(d));
78      }
79  
80      /**
81       * Check that the given double value can be exactly recovered from formatted string representation
82       * produced by the format instance.
83       * @param fmt format instance
84       * @param d input double value
85       */
86      private static void checkFormatAccuracy(final DoubleFunction<String> fmt, final double d) {
87          final String str = fmt.apply(d);
88          final double parsed = Double.parseDouble(str);
89          Assertions.assertEquals(d, parsed, () -> "Formatted double string [" + str + "] did not match input value");
90      }
91  
92      /**
93       * Check that the given format type correctly formats doubles when using the
94       * default configuration options. The format itself is not checked; only the
95       * fact that the input double can be successfully recovered using {@link Double#parseDouble(String)}
96       * is asserted.
97       * @param type format type
98       */
99      private static void checkFormatAccuracyWithDefaults(final DoubleFormat type) {
100         final DoubleFunction<String> fmt = type.builder().get();
101 
102         checkDefaultFormatSpecial(fmt);
103 
104         checkFormatAccuracy(fmt, Double.MIN_VALUE);
105         checkFormatAccuracy(fmt, -Double.MIN_VALUE);
106 
107         checkFormatAccuracy(fmt, Double.MIN_NORMAL);
108         checkFormatAccuracy(fmt, -Double.MIN_NORMAL);
109 
110         checkFormatAccuracy(fmt, Double.MAX_VALUE);
111         checkFormatAccuracy(fmt, -Double.MAX_VALUE);
112 
113         checkFormatAccuracy(fmt, Math.PI);
114         checkFormatAccuracy(fmt, Math.E);
115 
116         final Random rnd = new Random(10L);
117         final int cnt = 1000;
118         for (int i = 0; i < cnt; ++i) {
119             checkFormatAccuracy(fmt, randomDouble(rnd));
120         }
121     }
122 
123     private static void checkLocalizedFormat(final Locale loc, final String pattern,
124             final Function<Locale, DoubleFunction<String>> factory) {
125         // arrange
126         final DecimalFormat df = new DecimalFormat(pattern, DecimalFormatSymbols.getInstance(loc));
127         final DoubleFunction<String> fmt = factory.apply(loc);
128 
129         // act/assert
130         assertLocalizedFormatsAreEqual(0.0, df, fmt, loc);
131         assertLocalizedFormatsAreEqual(Double.POSITIVE_INFINITY, df, fmt, loc);
132         assertLocalizedFormatsAreEqual(Double.NEGATIVE_INFINITY, df, fmt, loc);
133         assertLocalizedFormatsAreEqual(Double.NaN, df, fmt, loc);
134 
135         assertLocalizedFormatsAreEqual(1.0, df, fmt, loc);
136         assertLocalizedFormatsAreEqual(-1.0, df, fmt, loc);
137         assertLocalizedFormatsAreEqual(Math.PI, df, fmt, loc);
138         assertLocalizedFormatsAreEqual(Math.E, df, fmt, loc);
139 
140         // Locales are tested using:
141         // DecimalFormat   DoubleFormat
142         // ##0.0##E0     : ENGINEERING  maPrecision=6
143         // 0.0##         : PLAIN        minDecimalExponent(-3)
144         // #,##0.0##     : PLAIN        minDecimalExponent(-3)
145         // 0.0##E0       : SCIENTIFIC   maPrecision=4
146         // The data should not test full precision (17 digits) of the PLAIN format.
147         // Set the exponent range to create decimals with exponents of approximately
148         // 10^7 to 10^-7: log2(1e7) = 23.25.
149         final Random rnd = new Random(12L);
150         final int minExp = -24;
151         final int maxExp = 24;
152         final int cnt = 1000;
153         for (int i = 0; i < cnt; ++i) {
154             assertLocalizedFormatsAreEqual(randomDouble(minExp, maxExp, rnd), df, fmt, loc);
155         }
156     }
157 
158     private static void checkLocalizedFormats(final String pattern, final Function<Locale, DoubleFunction<String>> factory) {
159         for (final Locale loc : Locale.getAvailableLocales()) {
160             checkLocalizedFormat(loc, pattern, factory);
161         }
162     }
163 
164     /**
165      * Create a random double value with exponent in the range {@code [minExp, maxExp]}.
166      * @param minExp minimum exponent; must be less than {@code maxExp}
167      * @param maxExp maximum exponent; must be greater than {@code minExp}
168      * @param rnd random number generator
169      * @return random double
170      */
171     private static double randomDouble(final int minExp, final int maxExp, final Random rnd) {
172         // Create random doubles using random bits in the sign bit and the mantissa.
173         final long mask = (1L << 52) - 1 | 1L << 63;
174         final long bits = rnd.nextLong() & mask;
175         // The exponent must be unsigned so + 1023 to the signed exponent
176         final long exp = rnd.nextInt(maxExp - minExp + 1) + minExp + 1023;
177         return Double.longBitsToDouble(bits | exp << 52);
178     }
179 
180     /**
181      * Create a random double value using the full range of exponent values.
182      * @param rnd random number generator
183      * @return random double
184      */
185     private static double randomDouble(final Random rnd) {
186         return randomDouble(Double.MIN_EXPONENT, Double.MAX_EXPONENT, rnd);
187     }
188 
189     static Stream<Arguments> testMaximumPrecision() {
190         return Stream.of(
191             // Example of different Double.toString representations across JDKs
192             // JDK 17: -9.3540047119774374E17
193             // JDK 21: -9.354004711977437E17
194             Arguments.of(DoubleFormat.PLAIN.builder().get(), -9.3540047119774374E17),
195             Arguments.of(DoubleFormat.SCIENTIFIC.builder().get(), -9.3540047119774374E17)
196         );
197     }
198 
199     /**
200      * Remove Unicode {@link Character#FORMAT format} characters from the given string.
201      * @param str input string
202      * @return input string with format characters removed
203      */
204     private static String trimFormatChars(final String str) {
205         final StringBuilder sb = new StringBuilder();
206         for (final char c : str.toCharArray()) {
207             if (Character.getType(c) != Character.FORMAT) {
208                 sb.append(c);
209             }
210         }
211         return sb.toString();
212     }
213 
214     @Test
215     void testBuilder_illegalArgs() {
216         // arrange
217         final DoubleFormat.Builder builder = DoubleFormat.PLAIN.builder();
218 
219         // act/assert
220         Assertions.assertThrows(NullPointerException.class, () -> builder.digits(null));
221         Assertions.assertThrows(IllegalArgumentException.class, () -> builder.digits("a"));
222         Assertions.assertThrows(IllegalArgumentException.class, () -> builder.digits("0123456789a"));
223 
224         Assertions.assertThrows(NullPointerException.class, () -> builder.exponentSeparator(null));
225         Assertions.assertThrows(NullPointerException.class, () -> builder.infinity(null));
226         Assertions.assertThrows(NullPointerException.class, () -> builder.nan(null));
227         Assertions.assertThrows(NullPointerException.class, () -> builder.formatSymbols(null));
228     }
229 
230     @Test
231     void testCustomDigitString() {
232         // arrange
233         final String digits = "abcdefghij";
234         final DoubleFunction<String> plain = DoubleFormat.PLAIN.builder().digits(digits).get();
235         final DoubleFunction<String> sci = DoubleFormat.SCIENTIFIC.builder().digits(digits).get();
236         final DoubleFunction<String> eng = DoubleFormat.ENGINEERING.builder().digits(digits).get();
237         final DoubleFunction<String> mixed = DoubleFormat.MIXED.builder().digits(digits).get();
238 
239         // act/assert
240         checkFormat(plain, 9876543210.0, "jihgfedcba.a");
241         checkFormat(sci, 9876543210.0, "j.ihgfedcbEj");
242         checkFormat(eng, 9876543210.0, "j.ihgfedcbEj");
243         checkFormat(mixed, 9876543210.0, "j.ihgfedcbEj");
244     }
245 
246     @Test
247     void testEngineering_custom() {
248         // act
249         final DoubleFunction<String> fmt = DoubleFormat.ENGINEERING.builder()
250                 .maxPrecision(3)
251                 .minDecimalExponent(-3)
252                 .allowSignedZero(false)
253                 .includeFractionPlaceholder(false)
254                 .decimalSeparator(',')
255                 .exponentSeparator("e")
256                 .infinity("inf")
257                 .nan("nan")
258                 .minusSign('!')
259                 .get();
260 
261         // act/assert
262         checkFormat(fmt, Double.NaN, "nan");
263         checkFormat(fmt, Double.POSITIVE_INFINITY, "inf");
264         checkFormat(fmt, Double.NEGATIVE_INFINITY, "!inf");
265 
266         checkFormat(fmt, 0.00001, "0");
267         checkFormat(fmt, -0.0001, "0");
268         checkFormat(fmt, 0.001, "1e!3");
269         checkFormat(fmt, -0.01, "!10e!3");
270         checkFormat(fmt, 0.1, "100e!3");
271         checkFormat(fmt, -0.0, "0");
272         checkFormat(fmt, 0.0, "0");
273         checkFormat(fmt, -1.0, "!1");
274         checkFormat(fmt, 10.0, "10");
275         checkFormat(fmt, -100.0, "!100");
276         checkFormat(fmt, 1000.0, "1e3");
277         checkFormat(fmt, -10000.0, "!10e3");
278         checkFormat(fmt, 100000.0, "100e3");
279         checkFormat(fmt, -1000000.0, "!1e6");
280         checkFormat(fmt, 10000000.0, "10e6");
281         checkFormat(fmt, -100000000.0, "!100e6");
282 
283         checkFormat(fmt, 1.25e-3, "1e!3");
284         checkFormat(fmt, -9.975e-4, "!1e!3");
285         checkFormat(fmt, 12345, "12,3e3");
286         checkFormat(fmt, -9_999_999, "!10e6");
287         checkFormat(fmt, 1.00001e7, "10e6");
288 
289         checkFormat(fmt, Double.MAX_VALUE, "180e306");
290         checkFormat(fmt, Double.MIN_VALUE, "0");
291         checkFormat(fmt, Double.MIN_NORMAL, "0");
292         checkFormat(fmt, Math.PI, "3,14");
293         checkFormat(fmt, Math.E, "2,72");
294     }
295 
296     @Test
297     void testEngineering_defaults() {
298         // act
299         final DoubleFunction<String> fmt = DoubleFormat.ENGINEERING.builder()
300                 .get();
301 
302         // act/assert
303         checkDefaultFormatSpecial(fmt);
304 
305         checkFormat(fmt, 0.00001, "10.0E-6");
306         checkFormat(fmt, -0.0001, "-100.0E-6");
307         checkFormat(fmt, 0.001, "1.0E-3");
308         checkFormat(fmt, -0.01, "-10.0E-3");
309         checkFormat(fmt, 0.1, "100.0E-3");
310         checkFormat(fmt, -0.0, "-0.0");
311         checkFormat(fmt, 0.0, "0.0");
312         checkFormat(fmt, -1.0, "-1.0");
313         checkFormat(fmt, 10.0, "10.0");
314         checkFormat(fmt, -100.0, "-100.0");
315         checkFormat(fmt, 1000.0, "1.0E3");
316         checkFormat(fmt, -10000.0, "-10.0E3");
317         checkFormat(fmt, 100000.0, "100.0E3");
318         checkFormat(fmt, -1000000.0, "-1.0E6");
319         checkFormat(fmt, 10000000.0, "10.0E6");
320         checkFormat(fmt, -100000000.0, "-100.0E6");
321 
322         checkFormat(fmt, 1.25e-3, "1.25E-3");
323         checkFormat(fmt, -9.975e-4, "-997.5E-6");
324         checkFormat(fmt, 12345, "12.345E3");
325         checkFormat(fmt, -9_999_999, "-9.999999E6");
326         checkFormat(fmt, 1.00001e7, "10.0001E6");
327 
328         checkFormat(fmt, Double.MAX_VALUE, "179.76931348623157E306");
329         checkFormat(fmt, Double.MIN_VALUE, "4.9E-324");
330         checkFormat(fmt, Double.MIN_NORMAL, "22.250738585072014E-309");
331         checkFormat(fmt, Math.PI, "3.141592653589793");
332         checkFormat(fmt, Math.E, "2.718281828459045");
333     }
334 
335     @Test
336     void testEngineering_localeFormatComparison() {
337         // act/assert
338         checkLocalizedFormats("##0.0##E0", loc -> DoubleFormat.ENGINEERING.builder()
339                 .maxPrecision(6)
340                 .alwaysIncludeExponent(true)
341                 .formatSymbols(DecimalFormatSymbols.getInstance(loc))
342                 .get());
343     }
344 
345     @Test
346     void testFormatAccuracy() {
347         // act/assert
348         checkFormatAccuracyWithDefaults(DoubleFormat.PLAIN);
349         checkFormatAccuracyWithDefaults(DoubleFormat.MIXED);
350         checkFormatAccuracyWithDefaults(DoubleFormat.SCIENTIFIC);
351         checkFormatAccuracyWithDefaults(DoubleFormat.ENGINEERING);
352     }
353 
354     /**
355      * Test formatting at the maximum precision. The formatting is based on the output
356      * of {@link Double#toString()}. If cannot create an extended precision text
357      * representation and is limited to 17 significant digits. This test verifies that
358      * formatting does not lose information that would be required to recreate the
359      * same double value.
360      */
361     @ParameterizedTest
362     @MethodSource
363     void testMaximumPrecision(final DoubleFunction<String> fmt, final double value) {
364         final String s = fmt.apply(value);
365         final double d = Double.parseDouble(s);
366         Assertions.assertEquals(value, d, () -> value + " formatted as " + s);
367     }
368 
369     @Test
370     void testMixed_custom() {
371         // arrange
372         final DoubleFunction<String> fmt = DoubleFormat.MIXED.builder()
373                 .maxPrecision(3)
374                 .minDecimalExponent(-3)
375                 .allowSignedZero(false)
376                 .includeFractionPlaceholder(false)
377                 .decimalSeparator(',')
378                 .plainFormatMaxDecimalExponent(4)
379                 .plainFormatMinDecimalExponent(-1)
380                 .exponentSeparator("e")
381                 .infinity("inf")
382                 .nan("nan")
383                 .minusSign('!')
384                 .get();
385 
386         // act/assert
387         checkFormat(fmt, Double.NaN, "nan");
388         checkFormat(fmt, Double.POSITIVE_INFINITY, "inf");
389         checkFormat(fmt, Double.NEGATIVE_INFINITY, "!inf");
390 
391         checkFormat(fmt, 0.00001, "0");
392         checkFormat(fmt, -0.0001, "0");
393         checkFormat(fmt, 0.001, "1e!3");
394         checkFormat(fmt, -0.01, "!1e!2");
395         checkFormat(fmt, 0.1, "0,1");
396         checkFormat(fmt, -0.0, "0");
397         checkFormat(fmt, 0.0, "0");
398         checkFormat(fmt, -1.0, "!1");
399         checkFormat(fmt, 10.0, "10");
400         checkFormat(fmt, -100.0, "!100");
401         checkFormat(fmt, 1000.0, "1000");
402         checkFormat(fmt, -10000.0, "!10000");
403         checkFormat(fmt, 100000.0, "1e5");
404         checkFormat(fmt, -1000000.0, "!1e6");
405         checkFormat(fmt, 10000000.0, "1e7");
406         checkFormat(fmt, -100000000.0, "!1e8");
407 
408         checkFormat(fmt, 1.25e-3, "1e!3");
409         checkFormat(fmt, -9.975e-4, "!1e!3");
410         checkFormat(fmt, 12345, "12300");
411         checkFormat(fmt, -9_999_999, "!1e7");
412         checkFormat(fmt, 1.00001e7, "1e7");
413 
414         checkFormat(fmt, Double.MAX_VALUE, "1,8e308");
415         checkFormat(fmt, Double.MIN_VALUE, "0");
416         checkFormat(fmt, Double.MIN_NORMAL, "0");
417         checkFormat(fmt, Math.PI, "3,14");
418         checkFormat(fmt, Math.E, "2,72");
419     }
420 
421     @Test
422     void testMixed_defaults() {
423         // arrange
424         testMixed_defaults(DoubleFormat.MIXED.builder().get());
425     }
426 
427     private void testMixed_defaults(final DoubleFunction<String> fmt) {
428         // act/assert
429         checkDefaultFormatSpecial(fmt);
430 
431         checkFormat(fmt, 0.00001, "1.0E-5");
432         checkFormat(fmt, -0.0001, "-1.0E-4");
433         checkFormat(fmt, 0.001, "0.001");
434         checkFormat(fmt, -0.01, "-0.01");
435         checkFormat(fmt, 0.1, "0.1");
436         checkFormat(fmt, -0.0, "-0.0");
437         checkFormat(fmt, 0.0, "0.0");
438         checkFormat(fmt, -1.0, "-1.0");
439         checkFormat(fmt, 10.0, "10.0");
440         checkFormat(fmt, -100.0, "-100.0");
441         checkFormat(fmt, 1000.0, "1000.0");
442         checkFormat(fmt, -10000.0, "-10000.0");
443         checkFormat(fmt, 100000.0, "100000.0");
444         checkFormat(fmt, -1000000.0, "-1000000.0");
445         checkFormat(fmt, 10000000.0, "1.0E7");
446         checkFormat(fmt, -100000000.0, "-1.0E8");
447 
448         checkFormat(fmt, 1.25e-3, "0.00125");
449         checkFormat(fmt, -9.975e-4, "-9.975E-4");
450         checkFormat(fmt, 12345, "12345.0");
451         checkFormat(fmt, -9_999_999, "-9999999.0");
452         checkFormat(fmt, 1.00001e7, "1.00001E7");
453 
454         checkFormat(fmt, Double.MAX_VALUE, "1.7976931348623157E308");
455         checkFormat(fmt, Double.MIN_VALUE, "4.9E-324");
456         checkFormat(fmt, Double.MIN_NORMAL, "2.2250738585072014E-308");
457         checkFormat(fmt, Math.PI, "3.141592653589793");
458         checkFormat(fmt, Math.E, "2.718281828459045");
459     }
460 
461     @Test
462     void testMixed_defaultsDeprecated() {
463         testMixed_defaults(DoubleFormat.MIXED.builder().build());
464     }
465 
466     @Test
467     void testPlain_custom() {
468         // arrange
469         final DoubleFunction<String> fmt = DoubleFormat.PLAIN.builder()
470             .maxPrecision(3)
471             .minDecimalExponent(-3)
472             .allowSignedZero(false)
473             .includeFractionPlaceholder(false)
474             .decimalSeparator(',')
475             .exponentSeparator("e")
476             .infinity("inf")
477             .nan("nan")
478             .minusSign('!')
479             .get();
480 
481         // act/assert
482         checkFormat(fmt, Double.NaN, "nan");
483         checkFormat(fmt, Double.POSITIVE_INFINITY, "inf");
484         checkFormat(fmt, Double.NEGATIVE_INFINITY, "!inf");
485 
486         checkFormat(fmt, 0.00001, "0");
487         checkFormat(fmt, -0.0001, "0");
488         checkFormat(fmt, 0.001, "0,001");
489         checkFormat(fmt, -0.01, "!0,01");
490         checkFormat(fmt, 0.1, "0,1");
491         checkFormat(fmt, -0.0, "0");
492         checkFormat(fmt, 0.0, "0");
493         checkFormat(fmt, -1.0, "!1");
494         checkFormat(fmt, 10.0, "10");
495         checkFormat(fmt, -100.0, "!100");
496         checkFormat(fmt, 1000.0, "1000");
497         checkFormat(fmt, -10000.0, "!10000");
498         checkFormat(fmt, 100000.0, "100000");
499         checkFormat(fmt, -1000000.0, "!1000000");
500         checkFormat(fmt, 10000000.0, "10000000");
501         checkFormat(fmt, -100000000.0, "!100000000");
502 
503         checkFormat(fmt, 1.25e-3, "0,001");
504         checkFormat(fmt, -9.975e-4, "!0,001");
505         checkFormat(fmt, 12345, "12300");
506         checkFormat(fmt, -9_999_999, "!10000000");
507         checkFormat(fmt, 1.00001e7, "10000000");
508 
509         checkFormat(fmt, Float.MAX_VALUE, "340000000000000000000000000000000000000");
510         checkFormat(fmt, -Float.MIN_VALUE, "0");
511         checkFormat(fmt, Float.MIN_NORMAL, "0");
512         checkFormat(fmt, Math.PI, "3,14");
513         checkFormat(fmt, Math.E, "2,72");
514     }
515 
516     @Test
517     void testPlain_defaults() {
518         // arrange
519         final DoubleFunction<String> fmt = DoubleFormat.PLAIN.builder()
520             .get();
521 
522         // act/assert
523         checkFormat(fmt, 0.00001, "0.00001");
524         checkFormat(fmt, -0.0001, "-0.0001");
525         checkFormat(fmt, 0.001, "0.001");
526         checkFormat(fmt, -0.01, "-0.01");
527         checkFormat(fmt, 0.1, "0.1");
528         checkFormat(fmt, -0.0, "-0.0");
529         checkFormat(fmt, 0.0, "0.0");
530         checkFormat(fmt, -1.0, "-1.0");
531         checkFormat(fmt, 10.0, "10.0");
532         checkFormat(fmt, -100.0, "-100.0");
533         checkFormat(fmt, 1000.0, "1000.0");
534         checkFormat(fmt, -10000.0, "-10000.0");
535         checkFormat(fmt, 100000.0, "100000.0");
536         checkFormat(fmt, -1000000.0, "-1000000.0");
537         checkFormat(fmt, 10000000.0, "10000000.0");
538         checkFormat(fmt, -100000000.0, "-100000000.0");
539 
540         checkFormat(fmt, 1.25e-3, "0.00125");
541         checkFormat(fmt, -9.975e-4, "-0.0009975");
542         checkFormat(fmt, 12345, "12345.0");
543         checkFormat(fmt, -9_999_999, "-9999999.0");
544         checkFormat(fmt, 1.00001e7, "10000100.0");
545 
546         checkFormat(fmt, Float.MAX_VALUE, "340282346638528860000000000000000000000.0");
547         checkFormat(fmt, -Float.MIN_VALUE, "-0.000000000000000000000000000000000000000000001401298464324817");
548         checkFormat(fmt, Float.MIN_NORMAL, "0.000000000000000000000000000000000000011754943508222875");
549         checkFormat(fmt, Math.PI, "3.141592653589793");
550         checkFormat(fmt, Math.E, "2.718281828459045");
551     }
552 
553     @Test
554     void testPlain_localeFormatComparison() {
555         // act/assert
556         checkLocalizedFormats("0.0##", loc -> DoubleFormat.PLAIN.builder()
557                 .minDecimalExponent(-3)
558                 .formatSymbols(DecimalFormatSymbols.getInstance(loc))
559                 .get());
560         checkLocalizedFormats("#,##0.0##", loc -> DoubleFormat.PLAIN.builder()
561                 .minDecimalExponent(-3)
562                 .groupThousands(true)
563                 .formatSymbols(DecimalFormatSymbols.getInstance(loc))
564                 .get());
565     }
566 
567     @Test
568     void testScientific_custom() {
569         // arrange
570         final DoubleFunction<String> fmt = DoubleFormat.SCIENTIFIC.builder()
571                 .maxPrecision(3)
572                 .minDecimalExponent(-3)
573                 .allowSignedZero(false)
574                 .includeFractionPlaceholder(false)
575                 .decimalSeparator(',')
576                 .exponentSeparator("e")
577                 .infinity("inf")
578                 .nan("nan")
579                 .minusSign('!')
580                 .get();
581 
582         // act/assert
583         checkFormat(fmt, Double.NaN, "nan");
584         checkFormat(fmt, Double.POSITIVE_INFINITY, "inf");
585         checkFormat(fmt, Double.NEGATIVE_INFINITY, "!inf");
586 
587         checkFormat(fmt, 0.00001, "0");
588         checkFormat(fmt, -0.0001, "0");
589         checkFormat(fmt, 0.001, "1e!3");
590         checkFormat(fmt, -0.01, "!1e!2");
591         checkFormat(fmt, 0.1, "1e!1");
592         checkFormat(fmt, -0.0, "0");
593         checkFormat(fmt, 0.0, "0");
594         checkFormat(fmt, -1.0, "!1");
595         checkFormat(fmt, 10.0, "1e1");
596         checkFormat(fmt, -100.0, "!1e2");
597         checkFormat(fmt, 1000.0, "1e3");
598         checkFormat(fmt, -10000.0, "!1e4");
599         checkFormat(fmt, 100000.0, "1e5");
600         checkFormat(fmt, -1000000.0, "!1e6");
601         checkFormat(fmt, 10000000.0, "1e7");
602         checkFormat(fmt, -100000000.0, "!1e8");
603 
604         checkFormat(fmt, 1.25e-3, "1e!3");
605         checkFormat(fmt, -9.975e-4, "!1e!3");
606         checkFormat(fmt, 12345, "1,23e4");
607         checkFormat(fmt, -9_999_999, "!1e7");
608         checkFormat(fmt, 1.00001e7, "1e7");
609 
610         checkFormat(fmt, Double.MAX_VALUE, "1,8e308");
611         checkFormat(fmt, Double.MIN_VALUE, "0");
612         checkFormat(fmt, Double.MIN_NORMAL, "0");
613         checkFormat(fmt, Math.PI, "3,14");
614         checkFormat(fmt, Math.E, "2,72");
615     }
616 
617     @Test
618     void testScientific_defaults() {
619         // arrange
620         final DoubleFunction<String> fmt = DoubleFormat.SCIENTIFIC.builder().get();
621 
622         // act/assert
623         checkDefaultFormatSpecial(fmt);
624 
625         checkFormat(fmt, 0.00001, "1.0E-5");
626         checkFormat(fmt, -0.0001, "-1.0E-4");
627         checkFormat(fmt, 0.001, "1.0E-3");
628         checkFormat(fmt, -0.01, "-1.0E-2");
629         checkFormat(fmt, 0.1, "1.0E-1");
630         checkFormat(fmt, -0.0, "-0.0");
631         checkFormat(fmt, 0.0, "0.0");
632         checkFormat(fmt, -1.0, "-1.0");
633         checkFormat(fmt, 10.0, "1.0E1");
634         checkFormat(fmt, -100.0, "-1.0E2");
635         checkFormat(fmt, 1000.0, "1.0E3");
636         checkFormat(fmt, -10000.0, "-1.0E4");
637         checkFormat(fmt, 100000.0, "1.0E5");
638         checkFormat(fmt, -1000000.0, "-1.0E6");
639         checkFormat(fmt, 10000000.0, "1.0E7");
640         checkFormat(fmt, -100000000.0, "-1.0E8");
641 
642         checkFormat(fmt, 1.25e-3, "1.25E-3");
643         checkFormat(fmt, -9.975e-4, "-9.975E-4");
644         checkFormat(fmt, 12345, "1.2345E4");
645         checkFormat(fmt, -9_999_999, "-9.999999E6");
646         checkFormat(fmt, 1.00001e7, "1.00001E7");
647 
648         checkFormat(fmt, Double.MAX_VALUE, "1.7976931348623157E308");
649         checkFormat(fmt, Double.MIN_VALUE, "4.9E-324");
650         checkFormat(fmt, Double.MIN_NORMAL, "2.2250738585072014E-308");
651         checkFormat(fmt, Math.PI, "3.141592653589793");
652         checkFormat(fmt, Math.E, "2.718281828459045");
653     }
654 
655     @Test
656     void testScientific_localeFormatComparison() {
657         // act/assert
658         checkLocalizedFormats("0.0##E0", loc -> DoubleFormat.SCIENTIFIC.builder()
659                 .maxPrecision(4)
660                 .alwaysIncludeExponent(true)
661                 .formatSymbols(DecimalFormatSymbols.getInstance(loc))
662                 .get());
663     }
664 }