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    *      https://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 static org.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertFalse;
21  import static org.junit.jupiter.api.Assertions.assertThrows;
22  import static org.junit.jupiter.api.Assertions.assertTrue;
23  
24  import java.math.BigDecimal;
25  import java.math.MathContext;
26  import java.math.RoundingMode;
27  import java.util.function.BiFunction;
28  
29  import org.apache.commons.rng.UniformRandomProvider;
30  import org.apache.commons.rng.simple.RandomSource;
31  import org.junit.jupiter.api.Test;
32  import org.junit.jupiter.api.function.Executable;
33  
34  class ParsedDecimalTest {
35  
36      private static final class FormatOptionsImpl implements ParsedDecimal.FormatOptions {
37  
38          private boolean includeFractionPlaceholder = true;
39  
40          private boolean signedZero = true;
41  
42          private char[] digits = "0123456789".toCharArray();
43  
44          private char decimalSeparator = '.';
45  
46          private char thousandsGroupingSeparator = ',';
47  
48          private boolean groupThousands;
49  
50          private char minusSign = '-';
51  
52          private String exponentSeparator = "E";
53  
54          private boolean alwaysIncludeExponent;
55  
56          @Override
57          public char getDecimalSeparator() {
58              return decimalSeparator;
59          }
60  
61          @Override
62          public char[] getDigits() {
63              return digits;
64          }
65  
66          @Override
67          public char[] getExponentSeparatorChars() {
68              return exponentSeparator.toCharArray();
69          }
70  
71          @Override
72          public char getGroupingSeparator() {
73              return thousandsGroupingSeparator;
74          }
75  
76          @Override
77          public char getMinusSign() {
78              return minusSign;
79          }
80  
81          @Override
82          public boolean isAlwaysIncludeExponent() {
83              return alwaysIncludeExponent;
84          }
85  
86          @Override
87          public boolean isGroupThousands() {
88              return groupThousands;
89          }
90  
91          @Override
92          public boolean isIncludeFractionPlaceholder() {
93              return includeFractionPlaceholder;
94          }
95  
96          @Override
97          public boolean isSignedZero() {
98              return signedZero;
99          }
100 
101         public void setAlwaysIncludeExponent(final boolean alwaysIncludeExponent) {
102             this.alwaysIncludeExponent = alwaysIncludeExponent;
103         }
104 
105         public void setDecimalSeparator(final char decimalSeparator) {
106             this.decimalSeparator = decimalSeparator;
107         }
108 
109         public void setDigitsFromString(final String digits) {
110             this.digits = digits.toCharArray();
111         }
112 
113         public void setExponentSeparator(final String exponentSeparator) {
114             this.exponentSeparator = exponentSeparator;
115         }
116 
117         public void setGroupThousands(final boolean groupThousands) {
118             this.groupThousands = groupThousands;
119         }
120 
121         public void setIncludeFractionPlaceholder(final boolean includeFractionPlaceholder) {
122             this.includeFractionPlaceholder = includeFractionPlaceholder;
123         }
124 
125         public void setMinusSign(final char minusSign) {
126             this.minusSign = minusSign;
127         }
128 
129         public void setSignedZero(final boolean signedZero) {
130             this.signedZero = signedZero;
131         }
132 
133         public void setThousandsGroupingSeparator(final char thousandsGroupingSeparator) {
134             this.thousandsGroupingSeparator = thousandsGroupingSeparator;
135         }
136     }
137 
138     private static void assertMaxPrecision(final double d, final int maxPrecision,
139             final boolean negative, final String digits, final int exponent) {
140         final ParsedDecimal dec = ParsedDecimal.from(d);
141         dec.maxPrecision(maxPrecision);
142 
143         assertSimpleDecimal(dec, negative, digits, exponent);
144     }
145 
146     private static void assertRound(final double d, final int roundExponent,
147             final boolean negative, final String digits, final int exponent) {
148         final ParsedDecimal dec = ParsedDecimal.from(d);
149         dec.round(roundExponent);
150 
151         assertSimpleDecimal(dec, negative, digits, exponent);
152     }
153 
154     private static void assertSimpleDecimal(final ParsedDecimal parsed, final boolean negative, final String digits,
155             final int exponent) {
156         assertEquals(negative, parsed.negative);
157         assertEquals(digits, digitString(parsed));
158         assertEquals(exponent, parsed.getExponent());
159         assertEquals(digits.length(), parsed.digitCount);
160         assertEquals(exponent, parsed.getScientificExponent() - digits.length() + 1);
161     }
162 
163     private static void assertThrowsWithMessage(final Executable fn, final Class<? extends Throwable> type,
164             final String msg) {
165         final Throwable exc = assertThrows(type, fn);
166         assertEquals(msg, exc.getMessage());
167     }
168 
169     private static void checkFrom(final double d, final String digits, final int exponent) {
170         final boolean negative = Math.signum(d) < 0;
171 
172         assertSimpleDecimal(ParsedDecimal.from(d), negative, digits, exponent);
173         assertSimpleDecimal(ParsedDecimal.from(-d), !negative, digits, exponent);
174     }
175 
176     private static void checkToEngineeringString(final double d, final String expected,
177             final ParsedDecimal.FormatOptions opts) {
178         checkToStringMethod(d, expected, ParsedDecimal::toEngineeringString, opts);
179 
180         // check the exponent value to make sure it is a multiple of 3
181         final String pos = ParsedDecimal.from(d).toEngineeringString(opts);
182         assertEquals(0, parseExponent(pos, opts) % 3);
183 
184         final String neg = ParsedDecimal.from(-d).toEngineeringString(opts);
185         assertEquals(0, parseExponent(neg, opts) % 3);
186     }
187 
188     private static void checkToPlainString(final double d, final String expected,
189             final ParsedDecimal.FormatOptions opts) {
190         checkToStringMethod(d, expected, ParsedDecimal::toPlainString, opts);
191     }
192 
193     private static void checkToScientificString(final double d, final String expected,
194             final ParsedDecimal.FormatOptions opts) {
195         checkToStringMethod(d, expected, ParsedDecimal::toScientificString, opts);
196     }
197 
198     private static void checkToStringMethod(final double d, final String expected,
199             final BiFunction<ParsedDecimal, ParsedDecimal.FormatOptions, String> fn,
200             final ParsedDecimal.FormatOptions opts) {
201 
202         final ParsedDecimal pos = ParsedDecimal.from(d);
203         final String actual = fn.apply(pos, opts);
204 
205         assertEquals(expected, actual);
206     }
207 
208     private static double createRandomDouble(final UniformRandomProvider rng) {
209         final long mask = (1L << 52) - 1 | 1L << 63;
210         final long bits = rng.nextLong() & mask;
211         final long exp = rng.nextInt(2045) + 1;
212         return Double.longBitsToDouble(bits | exp << 52);
213     }
214 
215     /** Gets the raw digits in the given decimal as a string.
216      * @param dec decimal instancE
217      * @return decimal digits as a string
218      */
219     private static String digitString(final ParsedDecimal dec) {
220         final StringBuilder sb = new StringBuilder();
221         for (int i = 0; i < dec.digitCount; ++i) {
222             sb.append(dec.digits[i]);
223         }
224         return sb.toString();
225     }
226 
227     private static int parseExponent(final String str, final ParsedDecimal.FormatOptions opts) {
228         final char[] expSep = opts.getExponentSeparatorChars();
229 
230         final int expStartIdx = str.indexOf(String.valueOf(expSep));
231         if (expStartIdx > -1) {
232             int expIdx = expStartIdx + expSep.length;
233 
234             boolean neg = false;
235             if (str.charAt(expIdx) == opts.getMinusSign()) {
236                 ++expIdx;
237                 neg = true;
238             }
239 
240             final String expStr = str.substring(expIdx);
241             final int val = Integer.parseInt(expStr);
242             return neg
243                     ? -val
244                     : val;
245         }
246 
247         return 0;
248     }
249 
250     @Test
251     void testFrom() {
252         // act/assert
253         checkFrom(0.0, "0", 0);
254 
255         checkFrom(1.0, "1", 0);
256         checkFrom(10.0, "1", 1);
257         checkFrom(100.0, "1", 2);
258         checkFrom(1000.0, "1", 3);
259         checkFrom(10000.0, "1", 4);
260 
261         checkFrom(0.1, "1", -1);
262         checkFrom(0.01, "1", -2);
263         checkFrom(0.001, "1", -3);
264         checkFrom(0.0001, "1", -4);
265         checkFrom(0.00001, "1", -5);
266 
267         checkFrom(1.2, "12", -1);
268         checkFrom(0.00971, "971", -5);
269         checkFrom(56300, "563", 2);
270 
271         checkFrom(123.0, "123", 0);
272         checkFrom(1230.0, "123", 1);
273         checkFrom(12300.0, "123", 2);
274         checkFrom(123000.0, "123", 3);
275 
276         checkFrom(12.3, "123", -1);
277         checkFrom(1.23, "123", -2);
278         checkFrom(0.123, "123", -3);
279         checkFrom(0.0123, "123", -4);
280 
281         checkFrom(1.987654321e270, "1987654321", 261);
282         checkFrom(1.987654321e-270, "1987654321", -279);
283 
284         checkFrom(Math.PI, "3141592653589793", -15);
285         checkFrom(Math.E, "2718281828459045", -15);
286 
287         checkFrom(Double.MAX_VALUE, "17976931348623157", 292);
288         checkFrom(Double.MIN_VALUE, "49", -325);
289         checkFrom(Double.MIN_NORMAL, "22250738585072014", -324);
290     }
291 
292     @Test
293     void testFromNotFinite() {
294         // arrange
295         final String msg = "Double is not finite";
296 
297         // act/assert
298         assertThrowsWithMessage(() -> ParsedDecimal.from(Double.NaN),
299                 IllegalArgumentException.class, msg);
300         assertThrowsWithMessage(() -> ParsedDecimal.from(Double.NEGATIVE_INFINITY),
301                 IllegalArgumentException.class, msg);
302         assertThrowsWithMessage(() -> ParsedDecimal.from(Double.POSITIVE_INFINITY),
303                 IllegalArgumentException.class, msg);
304     }
305 
306     @Test
307     void testIsZero() {
308         // act/assert
309         assertTrue(ParsedDecimal.from(0.0).isZero());
310         assertTrue(ParsedDecimal.from(-0.0).isZero());
311 
312         assertFalse(ParsedDecimal.from(1.0).isZero());
313         assertFalse(ParsedDecimal.from(-1.0).isZero());
314 
315         assertFalse(ParsedDecimal.from(Double.MIN_NORMAL).isZero());
316         assertFalse(ParsedDecimal.from(-Double.MIN_NORMAL).isZero());
317 
318         assertFalse(ParsedDecimal.from(Double.MAX_VALUE).isZero());
319         assertFalse(ParsedDecimal.from(-Double.MIN_VALUE).isZero());
320     }
321 
322     @Test
323     void testMaxPrecision() {
324         // arrange
325         final double d = 1.02576552;
326 
327         // act
328         assertMaxPrecision(d, 10, false, "102576552", -8);
329         assertMaxPrecision(d, 9, false, "102576552", -8);
330         assertMaxPrecision(d, 8, false, "10257655", -7);
331         assertMaxPrecision(d, 7, false, "1025766", -6);
332         assertMaxPrecision(d, 6, false, "102577", -5);
333         assertMaxPrecision(d, 5, false, "10258", -4);
334         assertMaxPrecision(d, 4, false, "1026", -3);
335         assertMaxPrecision(d, 3, false, "103", -2);
336         assertMaxPrecision(d, 2, false, "1", 0);
337         assertMaxPrecision(d, 1, false, "1", 0);
338 
339         assertMaxPrecision(d, 0, false, "102576552", -8);
340     }
341 
342     @Test
343     void testMaxPrecision_carry() {
344         // arrange
345         final double d = -999.0999e50;
346 
347         // act
348         assertMaxPrecision(d, 8, true, "9990999", 46);
349         assertMaxPrecision(d, 7, true, "9990999", 46);
350         assertMaxPrecision(d, 6, true, "9991", 49);
351         assertMaxPrecision(d, 5, true, "9991", 49);
352         assertMaxPrecision(d, 4, true, "9991", 49);
353         assertMaxPrecision(d, 3, true, "999", 50);
354         assertMaxPrecision(d, 2, true, "1", 53);
355         assertMaxPrecision(d, 1, true, "1", 53);
356 
357         assertMaxPrecision(d, 0, true, "9990999", 46);
358     }
359 
360     @Test
361     void testMaxPrecision_halfEvenRounding() {
362         // act/assert
363         // Test values taken from RoundingMode.HALF_EVEN javadocs
364         assertMaxPrecision(5.5, 1, false, "6", 0);
365         assertMaxPrecision(2.5, 1, false, "2", 0);
366         assertMaxPrecision(1.6, 1, false, "2", 0);
367         assertMaxPrecision(1.1, 1, false, "1", 0);
368         assertMaxPrecision(1.0, 1, false, "1", 0);
369 
370         assertMaxPrecision(-1.0, 1, true, "1", 0);
371         assertMaxPrecision(-1.1, 1, true, "1", 0);
372         assertMaxPrecision(-1.6, 1, true, "2", 0);
373         assertMaxPrecision(-2.5, 1, true, "2", 0);
374         assertMaxPrecision(-5.5, 1, true, "6", 0);
375     }
376 
377     @Test
378     void testMaxPrecision_random() {
379         // arrange
380         final UniformRandomProvider rand = RandomSource.XO_RO_SHI_RO_128_PP.create(0L);
381         final ParsedDecimal.FormatOptions opts = new FormatOptionsImpl();
382 
383         for (int i = 0; i < 10_000; ++i) {
384             final double d = createRandomDouble(rand);
385             final int precision = rand.nextInt(20) + 1;
386             final MathContext ctx = new MathContext(precision, RoundingMode.HALF_EVEN);
387 
388             final ParsedDecimal dec = ParsedDecimal.from(d);
389 
390             // act
391             dec.maxPrecision(precision);
392 
393             // assert
394             assertEquals(new BigDecimal(Double.toString(d), ctx).doubleValue(),
395                     Double.parseDouble(dec.toScientificString(opts)));
396         }
397     }
398 
399     @Test
400     void testMaxPrecision_singleDigits() {
401         // act
402         assertMaxPrecision(9.0, 1, false, "9", 0);
403         assertMaxPrecision(1.0, 1, false, "1", 0);
404         assertMaxPrecision(0.0, 1, false, "0", 0);
405         assertMaxPrecision(-0.0, 1, true, "0", 0);
406         assertMaxPrecision(-1.0, 1, true, "1", 0);
407         assertMaxPrecision(-9.0, 1, true, "9", 0);
408     }
409 
410     @Test
411     void testRound_mixed() {
412         // arrange
413         final double a = 9.94e-10;
414         final double b = -3.1415;
415         final double c = 5.55e10;
416 
417         // act/assert
418         assertRound(a, -13, false, "994", -12);
419         assertRound(a, -12, false, "994", -12);
420         assertRound(a, -11, false, "99", -11);
421         assertRound(a, -10, false, "1", -9);
422         assertRound(a, -9, false, "1", -9);
423         assertRound(a, -8, false, "0", 0);
424 
425         assertRound(b, -5, true, "31415", -4);
426         assertRound(b, -4, true, "31415", -4);
427         assertRound(b, -3, true, "3142", -3);
428         assertRound(b, -2, true, "314", -2);
429         assertRound(b, -1, true, "31", -1);
430         assertRound(b, 0, true, "3", 0);
431         assertRound(b, 1, true, "0", 0);
432         assertRound(b, 2, true, "0", 0);
433 
434         assertRound(c, 7, false, "555", 8);
435         assertRound(c, 8, false, "555", 8);
436         assertRound(c, 9, false, "56", 9);
437         assertRound(c, 10, false, "6", 10);
438         assertRound(c, 11, false, "1", 11);
439         assertRound(c, 12, false, "0", 0);
440     }
441 
442     @Test
443     void testRound_nine() {
444         // arrange
445         final double a = 9e-10;
446         final double b = -9;
447         final double c = 9e10;
448 
449         // act/assert
450         assertRound(a, -11, false, "9", -10);
451         assertRound(a, -10, false, "9", -10);
452         assertRound(a, -9, false, "1", -9);
453 
454         assertRound(b, -1, true, "9", 0);
455         assertRound(b, 0, true, "9", 0);
456         assertRound(b, 1, true, "1", 1);
457 
458         assertRound(c, 9, false, "9", 10);
459         assertRound(c, 10, false, "9", 10);
460         assertRound(c, 11, false, "1", 11);
461     }
462 
463     @Test
464     void testRound_one() {
465         // arrange
466         final double a = 1e-10;
467         final double b = -1;
468         final double c = 1e10;
469 
470         // act/assert
471         assertRound(a, -11, false, "1", -10);
472         assertRound(a, -10, false, "1", -10);
473         assertRound(a, -9, false, "0", 0);
474 
475         assertRound(b, -1, true, "1", 0);
476         assertRound(b, 0, true, "1", 0);
477         assertRound(b, 1, true, "0", 0);
478 
479         assertRound(c, 9, false, "1", 10);
480         assertRound(c, 10, false, "1", 10);
481         assertRound(c, 11, false, "0", 0);
482     }
483 
484     @Test
485     void testStringMethodAccuracy_random() {
486         // arrange
487         final UniformRandomProvider rand = RandomSource.XO_RO_SHI_RO_128_PP.create(0L);
488 
489         final FormatOptionsImpl stdOpts = new FormatOptionsImpl();
490         final FormatOptionsImpl altOpts = new FormatOptionsImpl();
491         altOpts.setExponentSeparator("e");
492         altOpts.setIncludeFractionPlaceholder(false);
493 
494         double d;
495         for (int i = 0; i < 10_000; ++i) {
496             d = createRandomDouble(rand);
497 
498             // act/assert
499             assertEquals(d, Double.parseDouble(ParsedDecimal.from(d).toScientificString(stdOpts)));
500             assertEquals(d, Double.parseDouble(ParsedDecimal.from(d).toScientificString(altOpts)));
501 
502             assertEquals(d, Double.parseDouble(ParsedDecimal.from(d).toEngineeringString(stdOpts)));
503             assertEquals(d, Double.parseDouble(ParsedDecimal.from(d).toEngineeringString(altOpts)));
504 
505             assertEquals(d, Double.parseDouble(ParsedDecimal.from(d).toPlainString(stdOpts)));
506             assertEquals(d, Double.parseDouble(ParsedDecimal.from(d).toPlainString(altOpts)));
507         }
508     }
509 
510     @Test
511     void testStringMethodAccuracy_sequence() {
512         // arrange
513         final double min = -1000;
514         final double max = 1000;
515         final double delta = 0.1;
516 
517         final FormatOptionsImpl stdOpts = new FormatOptionsImpl();
518         final FormatOptionsImpl altOpts = new FormatOptionsImpl();
519         altOpts.setExponentSeparator("e");
520         altOpts.setIncludeFractionPlaceholder(false);
521 
522         assertEquals(10.0, Double.parseDouble(ParsedDecimal.from(10.0).toScientificString(stdOpts)));
523 
524         for (double d = min; d <= max; d += delta) {
525             // act/assert
526             assertEquals(d, Double.parseDouble(ParsedDecimal.from(d).toScientificString(stdOpts)));
527             assertEquals(d, Double.parseDouble(ParsedDecimal.from(d).toScientificString(altOpts)));
528 
529             assertEquals(d, Double.parseDouble(ParsedDecimal.from(d).toEngineeringString(stdOpts)));
530             assertEquals(d, Double.parseDouble(ParsedDecimal.from(d).toEngineeringString(altOpts)));
531 
532             assertEquals(d, Double.parseDouble(ParsedDecimal.from(d).toPlainString(stdOpts)));
533             assertEquals(d, Double.parseDouble(ParsedDecimal.from(d).toPlainString(altOpts)));
534         }
535     }
536 
537     @Test
538     void testStringMethods_customDigits() {
539         // arrange
540         final FormatOptionsImpl opts = new FormatOptionsImpl();
541         opts.setDigitsFromString("abcdefghij");
542 
543         // act/assert
544         assertEquals("b.a", ParsedDecimal.from(1.0).toPlainString(opts));
545         assertEquals("-a.abcd", ParsedDecimal.from(-0.0123).toPlainString(opts));
546         assertEquals("bc.de", ParsedDecimal.from(12.34).toPlainString(opts));
547         assertEquals("baaaa.a", ParsedDecimal.from(10000).toPlainString(opts));
548         assertEquals("jihgfedcba.a", ParsedDecimal.from(9876543210d).toPlainString(opts));
549 
550         assertEquals("b.a", ParsedDecimal.from(1.0).toScientificString(opts));
551         assertEquals("-b.cdE-c", ParsedDecimal.from(-0.0123).toScientificString(opts));
552         assertEquals("b.cdeEb", ParsedDecimal.from(12.34).toScientificString(opts));
553         assertEquals("b.aEe", ParsedDecimal.from(10000).toScientificString(opts));
554         assertEquals("j.ihgfedcbEj", ParsedDecimal.from(9876543210d).toScientificString(opts));
555 
556         assertEquals("b.a", ParsedDecimal.from(1.0).toEngineeringString(opts));
557         assertEquals("-bc.dE-d", ParsedDecimal.from(-0.0123).toEngineeringString(opts));
558         assertEquals("bc.de", ParsedDecimal.from(12.34).toEngineeringString(opts));
559         assertEquals("ba.aEd", ParsedDecimal.from(10000).toEngineeringString(opts));
560         assertEquals("j.ihgfedcbEj", ParsedDecimal.from(9876543210d).toEngineeringString(opts));
561     }
562 
563     @Test
564     void testToEngineeringString_altFormat() {
565         // arrange
566         final FormatOptionsImpl opts = new FormatOptionsImpl();
567         opts.setIncludeFractionPlaceholder(false);
568         opts.setSignedZero(false);
569         opts.setDecimalSeparator(',');
570         opts.setMinusSign('!');
571         opts.setExponentSeparator("x10^");
572         opts.setAlwaysIncludeExponent(true);
573 
574         // act/assert
575         checkToEngineeringString(0.0, "0x10^0", opts);
576         checkToEngineeringString(-0.0, "0x10^0", opts);
577         checkToEngineeringString(1.0, "1x10^0", opts);
578         checkToEngineeringString(1.5, "1,5x10^0", opts);
579 
580         checkToEngineeringString(10, "10x10^0", opts);
581 
582         checkToEngineeringString(-0.000000123, "!123x10^!9", opts);
583         checkToEngineeringString(12300000, "12,3x10^6", opts);
584 
585         checkToEngineeringString(Math.PI, "3,141592653589793x10^0", opts);
586         checkToEngineeringString(Math.E, "2,718281828459045x10^0", opts);
587 
588         checkToEngineeringString(-Double.MAX_VALUE, "!179,76931348623157x10^306", opts);
589         checkToEngineeringString(Double.MIN_VALUE, "4,9x10^!324", opts);
590         checkToEngineeringString(Double.MIN_NORMAL, "22,250738585072014x10^!309", opts);
591     }
592 
593     @Test
594     void testToEngineeringString_defaults() {
595         // arrange
596         final FormatOptionsImpl opts = new FormatOptionsImpl();
597 
598         // act/assert
599         checkToEngineeringString(0.0, "0.0", opts);
600         checkToEngineeringString(-0.0, "-0.0", opts);
601         checkToEngineeringString(1.0, "1.0", opts);
602         checkToEngineeringString(1.5, "1.5", opts);
603 
604         checkToEngineeringString(10, "10.0", opts);
605 
606         checkToEngineeringString(-0.000000123, "-123.0E-9", opts);
607         checkToEngineeringString(12300000, "12.3E6", opts);
608 
609         checkToEngineeringString(Math.PI, "3.141592653589793", opts);
610         checkToEngineeringString(Math.E, "2.718281828459045", opts);
611 
612         checkToEngineeringString(-Double.MAX_VALUE, "-179.76931348623157E306", opts);
613         checkToEngineeringString(Double.MIN_VALUE, "4.9E-324", opts);
614         checkToEngineeringString(Double.MIN_NORMAL, "22.250738585072014E-309", opts);
615     }
616 
617     @Test
618     void testToPlainString_altFormat() {
619         // arrange
620         final FormatOptionsImpl opts = new FormatOptionsImpl();
621         opts.setIncludeFractionPlaceholder(false);
622         opts.setSignedZero(false);
623         opts.setDecimalSeparator(',');
624         opts.setMinusSign('!');
625         opts.setThousandsGroupingSeparator('_');
626         opts.setGroupThousands(true);
627 
628         // act/assert
629         checkToPlainString(0.0, "0", opts);
630         checkToPlainString(-0.0, "0", opts);
631         checkToPlainString(1.0, "1", opts);
632         checkToPlainString(1.5, "1,5", opts);
633 
634         checkToPlainString(12, "12", opts);
635         checkToPlainString(123, "123", opts);
636         checkToPlainString(1234, "1_234", opts);
637         checkToPlainString(12345, "12_345", opts);
638         checkToPlainString(123456, "123_456", opts);
639         checkToPlainString(1234567, "1_234_567", opts);
640         checkToPlainString(12345678, "12_345_678", opts);
641         checkToPlainString(123456789, "123_456_789", opts);
642         checkToPlainString(1234567890, "1_234_567_890", opts);
643 
644         checkToPlainString(-0.000123, "!0,000123", opts);
645         checkToPlainString(12301, "12_301", opts);
646 
647         checkToPlainString(Math.PI, "3,141592653589793", opts);
648         checkToPlainString(Math.E, "2,718281828459045", opts);
649 
650         checkToPlainString(-12345.6789, "!12_345,6789", opts);
651         checkToPlainString(1.23e12, "1_230_000_000_000", opts);
652         checkToPlainString(1.23e-12, "0,00000000000123", opts);
653     }
654 
655     @Test
656     void testToPlainString_defaults() {
657         // arrange
658         final FormatOptionsImpl opts = new FormatOptionsImpl();
659 
660         // act/assert
661         checkToPlainString(0.0, "0.0", opts);
662         checkToPlainString(-0.0, "-0.0", opts);
663         checkToPlainString(1.0, "1.0", opts);
664         checkToPlainString(1.5, "1.5", opts);
665 
666         checkToPlainString(12, "12.0", opts);
667         checkToPlainString(123, "123.0", opts);
668         checkToPlainString(1234, "1234.0", opts);
669         checkToPlainString(12345, "12345.0", opts);
670         checkToPlainString(123456, "123456.0", opts);
671         checkToPlainString(1234567, "1234567.0", opts);
672         checkToPlainString(12345678, "12345678.0", opts);
673         checkToPlainString(123456789, "123456789.0", opts);
674         checkToPlainString(1234567890, "1234567890.0", opts);
675 
676         checkToPlainString(-0.000123, "-0.000123", opts);
677         checkToPlainString(12301, "12301.0", opts);
678 
679         checkToPlainString(Math.PI, "3.141592653589793", opts);
680         checkToPlainString(Math.E, "2.718281828459045", opts);
681 
682         checkToPlainString(-12345.6789, "-12345.6789", opts);
683         checkToPlainString(1.23e12, "1230000000000.0", opts);
684         checkToPlainString(1.23e-12, "0.00000000000123", opts);
685     }
686 
687     @Test
688     void testToScientificString_altFormats() {
689         // arrange
690         final FormatOptionsImpl opts = new FormatOptionsImpl();
691         opts.setIncludeFractionPlaceholder(false);
692         opts.setSignedZero(false);
693         opts.setDecimalSeparator(',');
694         opts.setMinusSign('!');
695         opts.setExponentSeparator("x10^");
696         opts.setAlwaysIncludeExponent(true);
697 
698         // act/assert
699         checkToScientificString(0.0, "0x10^0", opts);
700         checkToScientificString(-0.0, "0x10^0", opts);
701         checkToScientificString(1.0, "1x10^0", opts);
702         checkToScientificString(1.5, "1,5x10^0", opts);
703 
704         checkToScientificString(-0.000123, "!1,23x10^!4", opts);
705         checkToScientificString(12301, "1,2301x10^4", opts);
706 
707         checkToScientificString(Math.PI, "3,141592653589793x10^0", opts);
708         checkToScientificString(Math.E, "2,718281828459045x10^0", opts);
709 
710         checkToScientificString(-Double.MAX_VALUE, "!1,7976931348623157x10^308", opts);
711         checkToScientificString(Double.MIN_VALUE, "4,9x10^!324", opts);
712         checkToScientificString(Double.MIN_NORMAL, "2,2250738585072014x10^!308", opts);
713     }
714 
715     @Test
716     void testToScientificString_defaults() {
717         // arrange
718         final FormatOptionsImpl opts = new FormatOptionsImpl();
719 
720         // act/assert
721         checkToScientificString(0.0, "0.0", opts);
722         checkToScientificString(-0.0, "-0.0", opts);
723         checkToScientificString(1.0, "1.0", opts);
724         checkToScientificString(1.5, "1.5", opts);
725 
726         checkToScientificString(-0.000123, "-1.23E-4", opts);
727         checkToScientificString(12301, "1.2301E4", opts);
728 
729         checkToScientificString(Math.PI, "3.141592653589793", opts);
730         checkToScientificString(Math.E, "2.718281828459045", opts);
731 
732         checkToScientificString(-Double.MAX_VALUE, "-1.7976931348623157E308", opts);
733         checkToScientificString(Double.MIN_VALUE, "4.9E-324", opts);
734         checkToScientificString(Double.MIN_NORMAL, "2.2250738585072014E-308", opts);
735     }
736 }