1 package com.sharkysoft.printf.engine;
2
3 import com.sharkysoft.math.MantissaExponent;
4 import com.sharkysoft.string.StringToolbox;
5 import com.sharkysoft.util.UnreachableCodeException;
6 import java.math.BigDecimal;
7
8 /***
9 * Formats real numbers.
10 *
11 * <p><b>Details:</b> A <code>RealFormatter</code> formats numbers according to
12 * criteria selected by the user. The user can control various formatting
13 * parameters, such as field width, justification style (left-<wbr>align,
14 * right-<wbr>align, center, etc.), precision, etc.</p>
15 *
16 * <p>Since applications that format real numbers frequently format more than
17 * one real number with the same style, this class is designed to "remember" the
18 * currently selected formatting parameters so that they can be reused with each
19 * format call. This approach reduces the amount of parameters which must be
20 * passed during the format calls and results in more efficient execution.</p>
21 *
22 * <blockquote class="example">
23 *
24 * <p><b>Example:</b> Output the contents of a <code>float</code> array called
25 * <var>vals</var>, one per line, right-justified in a 25-character-wide
26 * field. Round each float to two decimal places, but always show the two
27 * decimal places even if they are zeros.</p>
28 *
29 *<blockquote><pre>
30 *float[] vals = {1.0F, (float) Math.PI, -22};
31 *RealFormatter formatter = new RealFormatter();
32 *formatter.setFieldWidth(25);
33 *formatter.setPadChar(':'); // non-space chosen for emphasis
34 *formatter.setAlignment(AlignmentMode.gpRight);
35 *formatter.setMinRightDigits(2);
36 *formatter.setMaxRightDigits(2);
37 *for (int i = 0; i < vals.length; ++ i)
38 * System.out.println(formatter.format(vals[i]));
39 *</pre></blockquote>
40 *
41 * <p>This produces the following output:</p>
42 *
43 *<blockquote><pre>
44 *:::::::::::::::::::::1.00
45 *:::::::::::::::::::::3.14
46 *:::::::::::::::::::-22.00
47 *</pre></blockquote>
48 *
49 * </blockquote>
50 *
51 * <p>Formatting parameters can be updated at any time, and doing so will change
52 * the way text is formatted afterwards. Methods in this class only support
53 * formatting for <code>radix</code>=10. For a detailed description of all
54 * user-<wbr>controllable parameters, see the documentation below and in the
55 * {@linkplain NumberFormatter superclass}.</p>
56 */
57 public class RealFormatter extends NumberFormatter
58 {
59
60
61
62
63
64
65
66 /***
67 * Exponent mpFormatter for scientific notation.
68 *
69 * <p><b>Details:</b> Property <code>ExponentFormatter</code> is the configurable
70 * {@link IntegerFormatter} used to render exponent integers when real values
71 * are rendered in scientific notation.</p>
72 *
73 * <p><b>Initial configuration:</b>
74 * <code>MinDigits</code>=2,
75 * <code>ZeroPrefix</code>="E",
76 * <code>PosPrefix</code>="E+",
77 * <code>NegPrefix</code>="E-".</p>
78 *
79 * @see #getExponentFormatter()
80 */
81 protected final IntegerFormatter mpExponentFormatter = new IntegerFormatter();
82
83 /***
84 * Retrieves ExponentFormatter property.
85 *
86 * @return current value
87 *
88 * @see #mpExponentFormatter
89 */
90 public IntegerFormatter getExponentFormatter()
91 {
92 return mpExponentFormatter;
93 }
94
95
96
97
98
99
100
101 /***
102 * Maximum digits left of decimal point.
103 *
104 * <p><b>Details:</b> Property <code>MaxLeftDigits</code> is the maximum
105 * number of digits to render left of the decimal point. The value will wrap
106 * like an odometer if it is too large. <b>Default value:</b>
107 * <code>Integer.MAX_VALUE</code>.</p>
108 *
109 * @see #getMaxLeftDigits()
110 * @see #setMaxLeftDigits(int)
111 */
112 protected int mnMaxLeftDigits = Integer.MAX_VALUE;
113
114 /***
115 * Retrieves MaxLeftDigits property.
116 *
117 * @return current value
118 *
119 * @see #mnMaxLeftDigits
120 */
121 public int getMaxLeftDigits()
122 {
123 return mnMaxLeftDigits;
124 }
125
126 /***
127 * Updates MaxLeftDigits property.
128 *
129 * <p><b>Details:</b> This setter updates the <code>MaxLeftDigits</code>
130 * property. The new value may not be smaller than zero. If the new value is
131 * smaller than <code>MinLeftDigits</code>, then <code>MinLeftDigits</code> will
132 * be set to this new value as well.</p>
133 *
134 * @param inMaxLeftDigits new value
135 *
136 * @see #mnMaxLeftDigits
137 */
138 public void setMaxLeftDigits(final int inMaxLeftDigits)
139 {
140 if (inMaxLeftDigits < 0)
141 throw new IllegalArgumentException("inMaxLeftDigits=" + inMaxLeftDigits);
142 if (inMaxLeftDigits < mnMinLeftDigits)
143 mnMinLeftDigits = inMaxLeftDigits;
144 mnMaxLeftDigits = inMaxLeftDigits;
145 }
146
147
148
149
150
151
152
153 /***
154 * Minimum digits left of decimal point.
155 *
156 * <p><b>Details:</b> Property <code>MinLeftDigits</code> is the minimum
157 * number of digits to render left of the decimal point. If the value isn't
158 * large enough, the value will be padded with the left pad digit
159 * ({@link #mcLeftPadDigit LeftPadDigit}). <b>Default value:</b> 1.</p>
160 *
161 * @see #getMinLeftDigits()
162 * @see #setMinLeftDigits(int)
163 */
164 protected int mnMinLeftDigits = 1;
165
166 /***
167 * Retrieves MinLeftDigits property.
168 *
169 * @return current value
170 *
171 * @see #mnMinLeftDigits
172 */
173 public int getMinLeftDigits()
174 {
175 return mnMinLeftDigits;
176 }
177
178 /***
179 * Updates MinLeftDigits property.
180 *
181 * <p><b>Details:</b> This setter updates the <code>MinLeftDigits</code>
182 * property. The new value may not be smaller than zero. If the new value is
183 * larger than <code>MaxLeftDigits</code>, then <code>MaxLeftDigits</code>
184 * will be set to the new value as well.</p>
185 *
186 * @param inMinLeftDigits new value
187 *
188 * @see #mnMinLeftDigits
189 */
190 public void setMinLeftDigits(final int inMinLeftDigits)
191 {
192 if (inMinLeftDigits < 0)
193 throw new IllegalArgumentException("inMinLeftDigits=" + inMinLeftDigits);
194 if (inMinLeftDigits > mnMaxLeftDigits)
195 mnMaxLeftDigits = inMinLeftDigits;
196 mnMinLeftDigits = inMinLeftDigits;
197 }
198
199
200
201
202
203
204
205 /***
206 * Left pad digit.
207 *
208 * <p><b>Details:</b> Property <code>LeftPadDigit</code> determines the digit
209 * or character to use for left-padding numbers. This character will be used
210 * only when the value of <code>MinLeftDigits</code> requires it. If this
211 * property is set to 0, the space character will be used, but the prefix
212 * string will appear <em>between</em> the pad digits and the actual number.
213 * Otherwise, the padding will appear between the prefix string and the
214 * number. <b>Default value:</b> '0'.</p>
215 *
216 * @see #getLeftPadDigit()
217 * @see #setLeftPadDigit(char)
218 */
219 protected char mcLeftPadDigit = '0';
220
221 /***
222 * Retrieves LeftPadDigit property.
223 *
224 * @return current value
225 *
226 * @see #mcLeftPadDigit
227 */
228 public char getLeftPadDigit()
229 {
230 return mcLeftPadDigit;
231 }
232
233 /***
234 * Updates LeftPadDigit property.
235 *
236 * @param icLeftPadDigit new value
237 *
238 * @see #mcLeftPadDigit
239 */
240 public void setLeftPadDigit(final char icLeftPadDigit)
241 {
242 mcLeftPadDigit = icLeftPadDigit;
243 }
244
245
246
247
248
249
250
251 /***
252 * Show decimal point when optional.
253 *
254 * <p><b>Details:</b> Property <code>ShowDecPoint</code> determines whether or
255 * not the decimal point will be included when it is possible render the
256 * number without it. <b>Default value: <code>false</code></b>.</p>
257 *
258 * @see #getShowDecPoint()
259 * @see #setShowDecPoint(boolean)
260 */
261 protected boolean mzShowDecPoint = false;
262
263 /***
264 * Retrieves ShowDecPoint property.
265 *
266 * @return current value
267 *
268 * @see #mzShowDecPoint
269 */
270 public boolean getShowDecPoint()
271 {
272 return mzShowDecPoint;
273 }
274
275 /***
276 * Updates ShowDecPoint property.
277 *
278 * @param izShowDecPoint
279 *
280 * @see #mzShowDecPoint
281 */
282 public void setShowDecPoint(final boolean izShowDecPoint)
283 {
284 mzShowDecPoint = izShowDecPoint;
285 }
286
287
288
289
290
291
292
293 /***
294 * Minimum digits right of the decimal point.
295 *
296 * <p><b>Details:</b> Property <code>MinRightDigits</code> determines the
297 * minimum number of digits that will be rendered right of the decimal point. If
298 * the fractional portion of the number isn't long enough to fill out the minimum
299 * number of digits, the extra places
300 * will be set to <code>RightPadDigit</code>. <b>Default value:</b> 0.
301 *
302 * @see #getMinRightDigits()
303 * @see #setMinRightDigits(int)
304 */
305 protected int mnMinRightDigits = 0;
306
307 /***
308 * Retrieves MinRightDigits property.
309 *
310 * @return current value
311 *
312 * @see #mnMinRightDigits
313 */
314 public int getMinRightDigits()
315 {
316 return mnMinRightDigits;
317 }
318
319 /***
320 * Updates MinRightDigits property.
321 *
322 * @param inMinRightDigits new value
323 *
324 * @see #mnMinRightDigits
325 */
326 public void setMinRightDigits(final int inMinRightDigits)
327 {
328 if (inMinRightDigits < 0)
329 throw new IllegalArgumentException("inMinRightDigits=" + inMinRightDigits);
330 if (inMinRightDigits > mnMaxRightDigits)
331 mnMaxRightDigits = inMinRightDigits;
332 mnMinRightDigits = inMinRightDigits;
333 }
334
335
336
337
338
339
340
341 /***
342 * Right pad digit.
343 *
344 * <p><b>Details:</b> Property <code>RightPadDigit</code> determines the digit
345 * or character to use for right-<wbr>padding numbers. This character will be used
346 * only when the
347 * value of <code>MinRightDigits</code> requires it. If this property is set
348 * to 0, the space
349 * character will be used, but the suffix string will appear <em>between</em>
350 * the pad digits and the actual number. Otherwise, the padding will appear
351 * between the suffix string and the number. <b>Default value:</b> '0'.</p>
352 *
353 * @see #getRightPadDigit()
354 * @see #setRightPadDigit(char)
355 */
356 protected char mcRightPadDigit = '0';
357
358 /***
359 * Retrieves RightPadDigit property.
360 *
361 * @return current value
362 *
363 * @see #mcRightPadDigit
364 */
365 public char getRightPadDigit()
366 {
367 return mcRightPadDigit;
368 }
369
370 /***
371 * Updates RightPadDigit property.
372 *
373 * @param icRightPadDigit new value
374 *
375 * @see #mcRightPadDigit
376 */
377 public void setRightPadDigit(final char icRightPadDigit)
378 {
379 mcRightPadDigit = icRightPadDigit;
380 }
381
382
383
384
385
386
387
388 /***
389 * Maximum digits right of decimal point.
390 *
391 * <p><b>Details:</b> Property <code>MaxRightDigits</code> determines the
392 * maximum number of digits to show on the right of the decimal point. If the
393 * fractional portion of the number is "longer" than this number of places,
394 * the value will be rounded. If, for a particular value being rendered, the
395 * configuration of <code>MaxRightDigits</code> and <code>SigDigits</code> is
396 * incompatible, the rendered value will rounded to the appropriate number of
397 * significant digits, and then the trailing zeros will be trimmed, if there
398 * are any, until the <code>MaxRightDigits</code> constraint is satisfied, or
399 * until there are no more trailing zeros to trim. <b>Default value:</b>
400 * <code>Integer.MAX_VALUE</code>.</p>
401 *
402 * @see #getMaxRightDigits()
403 * @see #setMaxRightDigits(int)
404 */
405 protected int mnMaxRightDigits = Integer.MAX_VALUE;
406
407 /***
408 * Retrieves MaxRightDigits property.
409 *
410 * @return current value
411 *
412 * @see #mnMaxRightDigits
413 */
414 public int getMaxRightDigits()
415 {
416 return mnMaxRightDigits;
417 }
418
419 /***
420 * Updates MaxRightDigits property.
421 *
422 * @param inMaxRightDigits new value
423 *
424 * @see #mnMaxRightDigits
425 */
426 public void setMaxRightDigits(final int inMaxRightDigits)
427 {
428 if (inMaxRightDigits < 0)
429 throw new IllegalArgumentException("inMaxRightDigits=" + inMaxRightDigits);
430 if (inMaxRightDigits < mnMinRightDigits)
431 mnMinRightDigits = inMaxRightDigits;
432 mnMaxRightDigits = inMaxRightDigits;
433 }
434
435
436
437
438
439
440
441 /***
442 * Default constructor.
443 *
444 * <p><b>Details:</b> This constructor initializes a new instance with the
445 * default value for each property, as specified in the property
446 * documentation.</p>
447 */
448 public RealFormatter()
449 {
450 super();
451 mpExponentFormatter.msZeroPrefix = "E";
452 mpExponentFormatter.msNegPrefix = "E-";
453 mpExponentFormatter.msPosPrefix = "E+";
454 mpExponentFormatter.mnMinDigits = 2;
455 }
456
457
458
459
460
461
462
463 private final String format(BigDecimal ipValue, final String isExponentSuffix)
464 {
465 if (mnRadix != 10)
466 throw new IllegalArgumentException("mnRadix=" + mnRadix);
467
468 final String vsPrefix, vsSuffix;
469 switch (ipValue.signum())
470 {
471 case -1:
472 vsPrefix = msNegPrefix;
473 vsSuffix = msNegSuffix;
474 ipValue = ipValue.negate();
475 break;
476 case 0:
477 vsPrefix = msZeroPrefix;
478 vsSuffix = msZeroSuffix;
479 break;
480 case +1:
481 vsPrefix = msPosPrefix;
482 vsSuffix = msPosSuffix;
483 break;
484 default:
485 throw new UnreachableCodeException();
486 }
487 final String vsString;
488
489
490 if (mnSigDigits > 0)
491 {
492 ipValue = setSignificantDigits(ipValue, mnSigDigits);
493 final int vnTrimAmt = ipValue.scale() - mnMaxRightDigits;
494 if (vnTrimAmt > 0)
495 {
496
497
498
499
500 final StringBuffer vpBuff = new StringBuffer(ipValue.toString());
501 int vnLast = vpBuff.length() - 1;
502 for (int vnI = 0; vnI < vnTrimAmt; ++ vnI)
503 {
504 if (vpBuff.charAt(vnLast) != '0')
505 break;
506 -- vnLast;
507 }
508 vpBuff.setLength(vnLast + 1);
509 vsString = vpBuff.toString();
510 }
511 else
512 vsString = ipValue.toString();
513 }
514 else
515 {
516
517
518
519 if (ipValue.scale() > mnMaxRightDigits)
520 ipValue = ipValue.setScale(mnMaxRightDigits, BigDecimal.ROUND_HALF_UP);
521 vsString = ipValue.toString();
522 }
523
524 final String vsLeftDigits, vsRightDigits;
525 {
526 int vnDpPos = vsString.indexOf('.');
527 if (vnDpPos == -1)
528 {
529 vsLeftDigits = vsString;
530 vsRightDigits = "";
531 }
532 else
533 {
534 int vnLds = vnDpPos - mnMaxLeftDigits;
535 if (vnLds < 0)
536 vnLds = 0;
537 vsLeftDigits = vsString.substring(vnLds, vnDpPos);
538 ++ vnDpPos;
539 if (vnDpPos == vsString.length())
540 vsRightDigits = "";
541 else
542 vsRightDigits = vsString.substring(vnDpPos);
543 }
544 }
545
546 final String vsLeftExtender;
547 {
548 final int vnExtra = mnMinLeftDigits - vsLeftDigits.length();
549 if (vnExtra > 0)
550 {
551 if (mcLeftPadDigit != 0)
552 vsLeftExtender = StringToolbox.repeat(mcLeftPadDigit, vnExtra);
553 else
554 vsLeftExtender = StringToolbox.repeat(' ', vnExtra);
555 }
556 else
557 vsLeftExtender = "";
558 }
559
560 boolean vzNeedDecPoint = mzShowDecPoint;
561
562 final String vsRightExtender;
563 {
564 final int vnLength = vsRightDigits.length();
565 if (vnLength > 0)
566 vzNeedDecPoint = true;
567 final int vnExtra = mnMinRightDigits - vsRightDigits.length();
568 if (vnExtra > 0)
569 {
570 if (mcRightPadDigit != 0)
571 {
572 vsRightExtender = StringToolbox.repeat(mcRightPadDigit, vnExtra);
573 if (mcRightPadDigit != ' ')
574 vzNeedDecPoint = true;
575 }
576 else
577 vsRightExtender = StringToolbox.repeat(' ', vnExtra);
578 }
579 else
580 vsRightExtender = "";
581 }
582 final StringBuffer vpLeftSide = new StringBuffer();
583 final StringBuffer vpRightSide = new StringBuffer();
584
585 if (mnAlignment == AlignmentMode.FULL)
586 {
587 vpLeftSide.append(vsPrefix);
588 vpRightSide.append(vsLeftExtender);
589 }
590 else
591 {
592 if (mcLeftPadDigit != 0)
593 {
594 vpRightSide.append(vsPrefix);
595 vpRightSide.append(vsLeftExtender);
596 }
597 else
598 {
599 vpRightSide.append(vsLeftExtender);
600 vpRightSide.append(vsPrefix);
601 }
602 }
603
604 vpRightSide.append(vsLeftDigits);
605 if (vzNeedDecPoint)
606 vpRightSide.append('.');
607 vpRightSide.append(vsRightDigits);
608
609 if (mcRightPadDigit != 0)
610 {
611 vpRightSide.append(vsRightExtender);
612 vpRightSide.append(isExponentSuffix).append(vsSuffix);
613 }
614 else
615 {
616 vpRightSide.append(isExponentSuffix).append(vsSuffix);
617 vpRightSide.append(vsRightExtender);
618 }
619
620 return format(vpLeftSide.toString(), vpRightSide.toString());
621 }
622
623 /***
624 * Formats real value.
625 *
626 * <p><b>Details:</b> <code>format</code> renders the given real value
627 * (<code>ipValue</code>) into the format specified by the current property
628 * configuration. The value is rendered into scientific notation.</p>
629 *
630 * @param ipValue the real value
631 * @return the formatted value
632 */
633 public final String format(final MantissaExponent ipValue)
634 {
635 final BigDecimal vpMantissa = ipValue.getMantissa();
636 if (vpMantissa == null)
637 switch (ipValue.getExponent())
638 {
639 case MantissaExponent.NAN:
640 return formatSpecial(msPosPrefix + "nan");
641 case MantissaExponent.NEG_INF:
642 return formatSpecial(msNegPrefix + "inf");
643 case MantissaExponent.POS_INF:
644 return formatSpecial(msZeroPrefix + "inf");
645 default:
646 throw new UnreachableCodeException("getExponent returned spurious value");
647 }
648 return format(vpMantissa, mpExponentFormatter.format(ipValue.getExponent()));
649 }
650
651 /***
652 * Formats real value.
653 *
654 * <p><b>Details:</b> <code>format</code> renders the given real value
655 * (<code>ipValue</code>) into the format specified by the current property
656 * configuration.</p>
657 *
658 * @param ipValue the real value
659 * @return the formatted value
660 */
661 public final String format(final BigDecimal ipValue)
662 {
663 return format(ipValue, "");
664 }
665
666 private String formatSpecial(final String isSpecial)
667 {
668 switch (mnAlignment)
669 {
670 case AlignmentMode.FULL:
671 return format("", isSpecial, ' ');
672 default:
673 return format(isSpecial, ' ');
674 }
675 }
676
677 /***
678 * Formats real value.
679 *
680 * <p><b>Details:</b> <code>format</code> renders the given real value
681 * (<code>ifValue</code>) into the format specified by the current property
682 * configuration.</p>
683 *
684 * @param ifValue the real value
685 * @return the formatted value
686 */
687 public final String format(final float ifValue)
688 {
689 if (ifValue == Float.NEGATIVE_INFINITY)
690 return formatSpecial(msNegPrefix + "inf");
691 if (ifValue == Float.POSITIVE_INFINITY)
692 return formatSpecial(msPosPrefix + "inf");
693 if (Float.isNaN(ifValue))
694 return formatSpecial(msZeroPrefix + "nan");
695 return format(new BigDecimal(ifValue));
696 }
697
698 /***
699 * Formats real value.
700 *
701 * <p><b>Details:</b> <code>format</code> renders the given real value
702 * (<code>idValue</code>) into the format specified by the current property
703 * configuration.</p>
704 *
705 * @param idValue the real value
706 * @return the formatted value
707 */
708 public final String format(final double idValue)
709 {
710 if (idValue == Double.NEGATIVE_INFINITY)
711 return formatSpecial(msNegPrefix + "inf");
712 if (idValue == Double.POSITIVE_INFINITY)
713 return formatSpecial(msPosPrefix + "inf");
714 if (Double.isNaN(idValue))
715 return formatSpecial(msZeroPrefix + "nan");
716 return format(new BigDecimal(idValue));
717 }
718
719 }
720