View Javadoc

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 &lt; 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    //  ExponentFormatter  //
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    //  MaxLeftDigits  //
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   //  MinLeftDigits  //
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   //  LeftPadDigit  //
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   //  ShowDecPoint  //
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   //  MinRightDigits  //
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   //  RightPadDigit  //
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   //  MaxRightDigits  //
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   //  constructors  //
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   //  format  //
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     // Choose the prefix and suffix:
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     // If significant digits is specified, adjust the BigDecimal's significant
489     // digits by rounding:
490     if (mnSigDigits > 0)
491     {
492       ipValue = setSignificantDigits(ipValue, mnSigDigits);
493       final int vnTrimAmt = ipValue.scale() - mnMaxRightDigits;
494       if (vnTrimAmt > 0)
495       {
496         // We're in here because the sig_digits setting is incompatible with the
497         // max_right_digits setting for this particular value.  We will try to
498         // trim the current value's trailing zeros so that max_right_digits is
499         // satisfied.
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       // Round the number so that it does not have more than max_right_digits
517       // fractional digits.  Round with impunity since there is no sig_digits
518       // setting.
519       if (ipValue.scale() > mnMaxRightDigits)
520         ipValue = ipValue.setScale(mnMaxRightDigits, BigDecimal.ROUND_HALF_UP);
521       vsString = ipValue.toString();
522     }
523     // Separate the whole and fractional parts of the value:
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     // Make left extender:
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     // Assume that no decimal point will be needed until we discover otherwise:
560     boolean vzNeedDecPoint = mzShowDecPoint;
561     // Make right extender:
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     // Build leader:
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     // Append actual number:
604     vpRightSide.append(vsLeftDigits);
605     if (vzNeedDecPoint)
606       vpRightSide.append('.');
607     vpRightSide.append(vsRightDigits);
608     // Append trailer:
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     // Concatenate the two sides together and finish formatting:
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