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