View Javadoc
1   /*
2    * J.A.D.E. Java(TM) Addition to Default Environment.
3    * Latest release available at http://jade.dautelle.com/
4    * This class is public domain (not copyrighted).
5    */
6   package org.codehaus.plexus.util;
7   
8   /**
9    * <p>
10   * This class provides utility methods to parse <code>CharSequence</code> into primitive types and to format primitive
11   * types into <code>StringBuffer</code>.
12   * </p>
13   * <p>
14   * Methods from this utility class <b>do not create temporary objects</b> and are typically faster than standard library
15   * methods (e.g {@link #parseDouble} is up to 15x faster than <code>Double.parseDouble</code>).
16   * </p>
17   * For class instances, formatting is typically performed using specialized <code>java.text.Format</code>
18   * (<code>Locale</code> sensitive) and/or using conventional methods (class sensitive). For example:
19   *
20   * <pre>
21   *     public class Foo {
22   *         public static Foo valueOf(CharSequence chars) {...} // Parses.
23   *         public StringBuffer appendTo(StringBuffer sb) {...} // Formats.
24   *         public String toString() {
25   *             return appendTo(new StringBuffer()).toString();
26   *         }
27   *     }
28   * </pre>
29   * <p>
30   * <i> This class is <b>public domain</b> (not copyrighted).</i>
31   * </p>
32   *
33   * @author <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a>
34   * @version 4.6, June 22, 2003
35   */
36  public final class TypeFormat {
37  
38      /**
39       * Holds the characters used to represent numbers.
40       */
41      private static final char[] DIGITS = {
42          '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
43          'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
44      };
45  
46      /**
47       * Default constructor (forbids derivation).
48       */
49      private TypeFormat() {}
50  
51      /**
52       * Searches for a particular sequence within a character sequence (general purpose parsing function).
53       *
54       * @param pattern the character sequence to search for.
55       * @param chars the character sequence being searched.
56       * @param fromIndex the index in <code>chars</code> to start the search from.
57       * @return the index in the range <code>[fromIndex, chars.length()-pattern.length()]</code> or <code>-1</code> if
58       *         the character sequence is not found.
59       */
60      public static int indexOf(CharSequence pattern, CharSequence chars, int fromIndex) {
61          int patternLength = pattern.length();
62          fromIndex = Math.max(0, fromIndex);
63          if (patternLength != 0) { // At least one character to search for.
64              char firstChar = pattern.charAt(0);
65              int last = chars.length() - patternLength;
66              for (int i = fromIndex; i <= last; i++) {
67                  if (chars.charAt(i) == firstChar) {
68                      boolean match = true;
69                      for (int j = 1; j < patternLength; j++) {
70                          if (chars.charAt(i + j) != pattern.charAt(j)) {
71                              match = false;
72                              break;
73                          }
74                      }
75                      if (match) {
76                          return i;
77                      }
78                  }
79              }
80              return -1;
81          } else {
82              return Math.min(0, fromIndex);
83          }
84      }
85  
86      /**
87       * Parses the specified <code>CharSequence</code> as a <code>boolean</code>.
88       *
89       * @param chars the character sequence to parse.
90       * @return the corresponding <code>boolean</code>.
91       */
92      public static boolean parseBoolean(CharSequence chars) {
93          return (chars.length() == 4)
94                  && (chars.charAt(0) == 't' || chars.charAt(0) == 'T')
95                  && (chars.charAt(1) == 'r' || chars.charAt(1) == 'R')
96                  && (chars.charAt(2) == 'u' || chars.charAt(2) == 'U')
97                  && (chars.charAt(3) == 'e' || chars.charAt(3) == 'E');
98      }
99  
100     /**
101      * Parses the specified <code>CharSequence</code> as a signed decimal <code>short</code>.
102      *
103      * @param chars the character sequence to parse.
104      * @return <code>parseShort(chars, 10)</code>
105      * @throws NumberFormatException if the specified character sequence does not contain a parsable <code>short</code>.
106      * @see #parseShort(CharSequence, int)
107      */
108     public static short parseShort(CharSequence chars) {
109         return parseShort(chars, 10);
110     }
111 
112     /**
113      * Parses the specified <code>CharSequence</code> as a signed <code>short</code> in the specified radix. The
114      * characters in the string must all be digits of the specified radix, except the first character which may be a
115      * plus sign <code>'+'</code> or a minus sign <code>'-'</code>.
116      *
117      * @param chars the character sequence to parse.
118      * @param radix the radix to be used while parsing.
119      * @return the corresponding <code>short</code>.
120      * @throws NumberFormatException if the specified character sequence does not contain a parsable <code>short</code>.
121      */
122     public static short parseShort(CharSequence chars, int radix) {
123         try {
124             boolean isNegative = (chars.charAt(0) == '-') ? true : false;
125             int result = 0;
126             int limit = (isNegative) ? Short.MIN_VALUE : -Short.MAX_VALUE;
127             int multmin = limit / radix;
128             int length = chars.length();
129             int i = (isNegative || (chars.charAt(0) == '+')) ? 1 : 0;
130             while (true) {
131                 int digit = Character.digit(chars.charAt(i), radix);
132                 int tmp = result * radix;
133                 if ((digit < 0) || (result < multmin) || (tmp < limit + digit)) { // Overflow.
134                     throw new NumberFormatException("For input characters: \"" + chars.toString() + "\"");
135                 }
136                 // Accumulates negatively.
137                 result = tmp - digit;
138                 if (++i >= length) {
139                     break;
140                 }
141             }
142             return (short) (isNegative ? result : -result);
143         } catch (IndexOutOfBoundsException e) {
144             throw new NumberFormatException("For input characters: \"" + chars.toString() + "\"");
145         }
146     }
147 
148     /**
149      * Parses the specified <code>CharSequence</code> as a signed decimal <code>int</code>.
150      *
151      * @param chars the character sequence to parse.
152      * @return <code>parseInt(chars, 10)</code>
153      * @throws NumberFormatException if the specified character sequence does not contain a parsable <code>int</code>.
154      * @see #parseInt(CharSequence, int)
155      */
156     public static int parseInt(CharSequence chars) {
157         return parseInt(chars, 10);
158     }
159 
160     /**
161      * Parses the specified <code>CharSequence</code> as a signed <code>int</code> in the specified radix. The
162      * characters in the string must all be digits of the specified radix, except the first character which may be a
163      * plus sign <code>'+'</code> or a minus sign <code>'-'</code>.
164      *
165      * @param chars the character sequence to parse.
166      * @param radix the radix to be used while parsing.
167      * @return the corresponding <code>int</code>.
168      * @throws NumberFormatException if the specified character sequence does not contain a parsable <code>int</code>.
169      */
170     public static int parseInt(CharSequence chars, int radix) {
171         try {
172             boolean isNegative = (chars.charAt(0) == '-') ? true : false;
173             int result = 0;
174             int limit = (isNegative) ? Integer.MIN_VALUE : -Integer.MAX_VALUE;
175             int multmin = limit / radix;
176             int length = chars.length();
177             int i = (isNegative || (chars.charAt(0) == '+')) ? 1 : 0;
178             while (true) {
179                 int digit = Character.digit(chars.charAt(i), radix);
180                 int tmp = result * radix;
181                 if ((digit < 0) || (result < multmin) || (tmp < limit + digit)) { // Overflow.
182                     throw new NumberFormatException("For input characters: \"" + chars.toString() + "\"");
183                 }
184                 // Accumulates negatively to avoid surprises near MAX_VALUE
185                 result = tmp - digit;
186                 if (++i >= length) {
187                     break;
188                 }
189             }
190             return isNegative ? result : -result;
191         } catch (IndexOutOfBoundsException e) {
192             throw new NumberFormatException("For input characters: \"" + chars.toString() + "\"");
193         }
194     }
195 
196     /**
197      * Parses the specified <code>CharSequence</code> as a signed decimal <code>long</code>.
198      *
199      * @param chars the character sequence to parse.
200      * @return <code>parseLong(chars, 10)</code>
201      * @throws NumberFormatException if the specified character sequence does not contain a parsable <code>long</code>.
202      * @see #parseLong(CharSequence, int)
203      */
204     public static long parseLong(CharSequence chars) {
205         return parseLong(chars, 10);
206     }
207 
208     /**
209      * Parses the specified <code>CharSequence</code> as a signed <code>long</code> in the specified radix. The
210      * characters in the string must all be digits of the specified radix, except the first character which may be a
211      * plus sign <code>'+'</code> or a minus sign <code>'-'</code>.
212      *
213      * @param chars the character sequence to parse.
214      * @param radix the radix to be used while parsing.
215      * @return the corresponding <code>long</code>.
216      * @throws NumberFormatException if the specified character sequence does not contain a parsable <code>long</code>.
217      */
218     public static long parseLong(CharSequence chars, int radix) {
219         try {
220             boolean isNegative = (chars.charAt(0) == '-') ? true : false;
221             long result = 0;
222             long limit = (isNegative) ? Long.MIN_VALUE : -Long.MAX_VALUE;
223             long multmin = limit / radix;
224             int length = chars.length();
225             int i = (isNegative || (chars.charAt(0) == '+')) ? 1 : 0;
226             while (true) {
227                 int digit = Character.digit(chars.charAt(i), radix);
228                 long tmp = result * radix;
229                 if ((digit < 0) || (result < multmin) || (tmp < limit + digit)) { // Overflow.
230                     throw new NumberFormatException("For input characters: \"" + chars.toString() + "\"");
231                 }
232                 // Accumulates negatively to avoid surprises near MAX_VALUE
233                 result = tmp - digit;
234                 if (++i >= length) {
235                     break;
236                 }
237             }
238             return isNegative ? result : -result;
239         } catch (IndexOutOfBoundsException e) {
240             throw new NumberFormatException("For input characters: \"" + chars.toString() + "\"");
241         }
242     }
243 
244     /**
245      * Parses this <code>CharSequence</code> as a <code>float</code>.
246      *
247      * @param chars the character sequence to parse.
248      * @return the float number represented by the specified character sequence.
249      * @throws NumberFormatException if the character sequence does not contain a parsable <code>float</code>.
250      */
251     public static float parseFloat(CharSequence chars) {
252         double d = parseDouble(chars);
253         if ((d >= Float.MIN_VALUE) && (d <= Float.MAX_VALUE)) {
254             return (float) d;
255         } else {
256             throw new NumberFormatException("Float overflow for input characters: \"" + chars.toString() + "\"");
257         }
258     }
259 
260     /**
261      * Parses this <code>CharSequence</code> as a <code>double</code>.
262      *
263      * @param chars the character sequence to parse.
264      * @return the double number represented by this character sequence.
265      * @throws NumberFormatException if the character sequence does not contain a parsable <code>double</code>.
266      */
267     public static double parseDouble(CharSequence chars) throws NumberFormatException {
268         try {
269             int length = chars.length();
270             double result = 0.0;
271             int exp = 0;
272 
273             boolean isNegative = (chars.charAt(0) == '-') ? true : false;
274             int i = (isNegative || (chars.charAt(0) == '+')) ? 1 : 0;
275 
276             // Checks special cases NaN or Infinity.
277             if ((chars.charAt(i) == 'N') || (chars.charAt(i) == 'I')) {
278                 if (chars.toString().equals("NaN")) {
279                     return Double.NaN;
280                 } else if (chars.subSequence(i, length).toString().equals("Infinity")) {
281                     return isNegative ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY;
282                 }
283             }
284 
285             // Reads decimal number.
286             boolean fraction = false;
287             while (true) {
288                 char c = chars.charAt(i);
289                 if ((c == '.') && (!fraction)) {
290                     fraction = true;
291                 } else if ((c == 'e') || (c == 'E')) {
292                     break;
293                 } else if ((c >= '0') && (c <= '9')) {
294                     result = result * 10 + (c - '0');
295                     if (fraction) {
296                         exp--;
297                     }
298                 } else {
299                     throw new NumberFormatException("For input characters: \"" + chars.toString() + "\"");
300                 }
301                 if (++i >= length) {
302                     break;
303                 }
304             }
305             result = isNegative ? -result : result;
306 
307             // Reads exponent (if any).
308             if (i < length) {
309                 i++;
310                 boolean negE = (chars.charAt(i) == '-') ? true : false;
311                 i = (negE || (chars.charAt(i) == '+')) ? i + 1 : i;
312                 int valE = 0;
313                 while (true) {
314                     char c = chars.charAt(i);
315                     if ((c >= '0') && (c <= '9')) {
316                         valE = valE * 10 + (c - '0');
317                         if (valE > 10000000) { // Hard-limit to avoid overflow.
318                             valE = 10000000;
319                         }
320                     } else {
321                         throw new NumberFormatException("For input characters: \"" + chars.toString() + "\"");
322                     }
323                     if (++i >= length) {
324                         break;
325                     }
326                 }
327                 exp += negE ? -valE : valE;
328             }
329 
330             // Returns product decimal number with exponent.
331             return multE(result, exp);
332 
333         } catch (IndexOutOfBoundsException e) {
334             throw new NumberFormatException("For input characters: \"" + chars.toString() + "\"");
335         }
336     }
337 
338     /**
339      * Formats the specified <code>boolean</code> and appends the resulting text to the <code>StringBuffer</code>
340      * argument.
341      *
342      * @param b a <code>boolean</code>.
343      * @param sb the <code>StringBuffer</code> to append.
344      * @return the specified <code>StringBuffer</code> object.
345      * @see #parseBoolean
346      */
347     public static StringBuffer format(boolean b, StringBuffer sb) {
348         return b ? sb.append("true") : sb.append("false");
349     }
350 
351     /**
352      * Formats the specified <code>short</code> and appends the resulting text (decimal representation) to the
353      * <code>StringBuffer</code> argument.
354      * <p>
355      * Note: This method is preferred to <code>StringBuffer.append(short)
356      *           </code> as it does not create temporary <code>String</code> objects (several times faster for small
357      * numbers).
358      * </p>
359      *
360      * @param s the <code>short</code> number.
361      * @param sb the <code>StringBuffer</code> to append.
362      * @return the specified <code>StringBuffer</code> object.
363      * @see #parseShort
364      */
365     public static StringBuffer format(short s, StringBuffer sb) {
366         return format((int) s, sb); // Forwards to int formatting (fast).
367     }
368 
369     /**
370      * Formats the specified <code>short</code> in the specified radix and appends the resulting text to the
371      * <code>StringBuffer</code> argument.
372      *
373      * @param s the <code>short</code> number.
374      * @param radix the radix.
375      * @param sb the <code>StringBuffer</code> to append.
376      * @return the specified <code>StringBuffer</code> object.
377      * @see #parseShort(CharSequence, int) throws IllegalArgumentException if radix is not in [2 .. 36] range.
378      */
379     public static StringBuffer format(short s, int radix, StringBuffer sb) {
380         return format((int) s, radix, sb); // Forwards to int formatting (fast).
381     }
382 
383     /**
384      * Formats the specified <code>int</code> and appends the resulting text (decimal representation) to the
385      * <code>StringBuffer</code> argument.
386      * <p>
387      * Note: This method is preferred to <code>StringBuffer.append(int)
388      *           </code> as it does not create temporary <code>String</code> objects (several times faster for small
389      * numbers).
390      * </p>
391      *
392      * @param i the <code>int</code> number.
393      * @param sb the <code>StringBuffer</code> to append.
394      * @return the specified <code>StringBuffer</code> object.
395      * @see #parseInt
396      */
397     public static StringBuffer format(int i, StringBuffer sb) {
398         if (i <= 0) {
399             if (i == Integer.MIN_VALUE) { // Negation would overflow.
400                 return sb.append("-2147483648"); // 11 char max.
401             } else if (i == 0) {
402                 return sb.append('0');
403             }
404             i = -i;
405             sb.append('-');
406         }
407         int j = 1;
408         for (; (j < 10) && (i >= INT_POW_10[j]); j++) {}
409         // POW_10[j] > i >= POW_10[j-1]
410         for (j--; j >= 0; j--) {
411             int pow10 = INT_POW_10[j];
412             int digit = i / pow10;
413             i -= digit * pow10;
414             sb.append(DIGITS[digit]);
415         }
416         return sb;
417     }
418 
419     private static final int[] INT_POW_10 = new int[10];
420 
421     static {
422         int pow = 1;
423         for (int i = 0; i < 10; i++) {
424             INT_POW_10[i] = pow;
425             pow *= 10;
426         }
427     }
428 
429     /**
430      * Formats the specified <code>int</code> in the specified radix and appends the resulting text to the
431      * <code>StringBuffer</code> argument.
432      *
433      * @param i the <code>int</code> number.
434      * @param radix the radix.
435      * @param sb the <code>StringBuffer</code> to append.
436      * @return the specified <code>StringBuffer</code> object.
437      * @see #parseInt(CharSequence, int) throws IllegalArgumentException if radix is not in [2 .. 36] range.
438      */
439     public static StringBuffer format(int i, int radix, StringBuffer sb) {
440         if (radix == 10) {
441             return format(i, sb); // Faster version.
442         } else if (radix < 2 || radix > 36) {
443             throw new IllegalArgumentException("radix: " + radix);
444         }
445         if (i < 0) {
446             sb.append('-');
447         } else {
448             i = -i;
449         }
450         format2(i, radix, sb);
451         return sb;
452     }
453 
454     private static void format2(int i, int radix, StringBuffer sb) {
455         if (i <= -radix) {
456             format2(i / radix, radix, sb);
457             sb.append(DIGITS[-(i % radix)]);
458         } else {
459             sb.append(DIGITS[-i]);
460         }
461     }
462 
463     /**
464      * Formats the specified <code>long</code> and appends the resulting text (decimal representation) to the
465      * <code>StringBuffer</code> argument.
466      * <p>
467      * Note: This method is preferred to <code>StringBuffer.append(long)
468      *           </code> as it does not create temporary <code>String</code> objects (several times faster for small
469      * numbers).
470      * </p>
471      *
472      * @param l the <code>long</code> number.
473      * @param sb the <code>StringBuffer</code> to append.
474      * @return the specified <code>StringBuffer</code> object.
475      * @see #parseLong
476      */
477     public static StringBuffer format(long l, StringBuffer sb) {
478         if (l <= 0) {
479             if (l == Long.MIN_VALUE) { // Negation would overflow.
480                 return sb.append("-9223372036854775808"); // 20 characters max.
481             } else if (l == 0) {
482                 return sb.append('0');
483             }
484             l = -l;
485             sb.append('-');
486         }
487         int j = 1;
488         for (; (j < 19) && (l >= LONG_POW_10[j]); j++) {}
489         // POW_10[j] > l >= POW_10[j-1]
490         for (j--; j >= 0; j--) {
491             long pow10 = LONG_POW_10[j];
492             int digit = (int) (l / pow10);
493             l -= digit * pow10;
494             sb.append(DIGITS[digit]);
495         }
496         return sb;
497     }
498 
499     private static final long[] LONG_POW_10 = new long[19];
500 
501     static {
502         long pow = 1;
503         for (int i = 0; i < 19; i++) {
504             LONG_POW_10[i] = pow;
505             pow *= 10;
506         }
507     }
508 
509     /**
510      * Formats the specified <code>long</code> in the specified radix and appends the resulting text to the
511      * <code>StringBuffer</code> argument.
512      *
513      * @param l the <code>long</code> number.
514      * @param radix the radix.
515      * @param sb the <code>StringBuffer</code> to append.
516      * @return the specified <code>StringBuffer</code> object.
517      * @see #parseLong(CharSequence, int) throws IllegalArgumentException if radix is not in [2 .. 36] range.
518      */
519     public static StringBuffer format(long l, int radix, StringBuffer sb) {
520         if (radix == 10) {
521             return format(l, sb); // Faster version.
522         } else if (radix < 2 || radix > 36) {
523             throw new IllegalArgumentException("radix: " + radix);
524         }
525         if (l < 0) {
526             sb.append('-');
527         } else {
528             l = -l;
529         }
530         format2(l, radix, sb);
531         return sb;
532     }
533 
534     private static void format2(long l, int radix, StringBuffer sb) {
535         if (l <= -radix) {
536             format2(l / radix, radix, sb);
537             sb.append(DIGITS[(int) -(l % radix)]);
538         } else {
539             sb.append(DIGITS[(int) -l]);
540         }
541     }
542 
543     /**
544      * Formats the specified <code>float</code> and appends the resulting text to the <code>StringBuffer</code>
545      * argument.
546      *
547      * @param f the <code>float</code> number.
548      * @param sb the <code>StringBuffer</code> to append.
549      * @return <code>format(f, 0.0f, sb)</code>
550      * @see #format(float, float, StringBuffer)
551      */
552     public static StringBuffer format(float f, StringBuffer sb) {
553         return format(f, 0.0f, sb);
554     }
555 
556     /**
557      * Formats the specified <code>float</code> and appends the resulting text to the <code>StringBuffer</code>
558      * argument; the number of significative digits is deduced from the specified precision. All digits at least as
559      * significant as the specified precision are represented. For example:
560      * <ul>
561      * <li><code>format(5.6f, 0.01f, sb)</code> appends <code>"5.60"</code></li>
562      * <li><code>format(5.6f, 0.1f, sb)</code> appends <code>"5.6"</code></li>
563      * <li><code>format(5.6f, 1f, sb)</code> appends <code>"6"</code></li>
564      * </ul>
565      * If the precision is <code>0.0f</code>, the precision is assumed to be the intrinsic <code>float</code> precision
566      * (64 bits IEEE 754 format); no formatting is performed, all significant digits are displayed and trailing zeros
567      * are removed.
568      *
569      * @param f the <code>float</code> number.
570      * @param precision the maximum weight of the last digit represented.
571      * @param sb the <code>StringBuffer</code> to append.
572      * @return the specified <code>StringBuffer</code> object.
573      * @throws IllegalArgumentException if the specified precision is negative or would result in too many digits (19+).
574      */
575     public static StringBuffer format(float f, float precision, StringBuffer sb) {
576         // Adjusts precision.
577         boolean precisionOnLastDigit;
578         if (precision > 0.0f) {
579             precisionOnLastDigit = true;
580         } else if (precision == 0.0f) {
581             if (f != 0.0f) {
582                 precisionOnLastDigit = false;
583                 precision = Math.max(Math.abs(f * FLOAT_RELATIVE_ERROR), Float.MIN_VALUE);
584             } else {
585                 return sb.append("0.0"); // Exact zero.
586             }
587         } else {
588             throw new IllegalArgumentException("precision: Negative values not allowed");
589         }
590         return format(f, precision, precisionOnLastDigit, sb);
591     }
592 
593     /**
594      * Formats the specified <code>double</code> and appends the resulting text to the <code>StringBuffer</code>
595      * argument.
596      * <p>
597      * Note : This method is preferred to <code>StringBuffer.append(double)
598      *            </code> or even <code>String.valueOf(double)</code> as it does not create temporary
599      * <code>String</code> or <code>
600      *            FloatingDecimal</code> objects (several times faster, e.g. 15x faster for
601      * <code>Double.MAX_VALUE</code>).
602      * </p>
603      *
604      * @param d the <code>double</code> number.
605      * @param sb the <code>StringBuffer</code> to append.
606      * @return <code>format(d, 0.0, sb)</code>
607      * @see #format(double, double, StringBuffer)
608      */
609     public static StringBuffer format(double d, StringBuffer sb) {
610         return format(d, 0.0, sb);
611     }
612 
613     /**
614      * Formats the specified <code>double</code> and appends the resulting text to the <code>StringBuffer</code>
615      * argument; the number of significand digits is specified as integer argument.
616      *
617      * @param d the <code>double</code> number.
618      * @param digits the number of significand digits (excludes exponent).
619      * @param sb the <code>StringBuffer</code> to append.
620      * @return the specified <code>StringBuffer</code> object.
621      * @throws IllegalArgumentException if the number of digits is not in range <code>[1..19]</code>.
622      */
623     public static StringBuffer format(double d, int digits, StringBuffer sb) {
624         if ((digits >= 1) && (digits <= 19)) {
625             double precision = Math.abs(d / DOUBLE_POW_10[digits - 1]);
626             return format(d, precision, sb);
627         } else {
628             throw new java.lang.IllegalArgumentException("digits: " + digits + " is not in range [1 .. 19]");
629         }
630     }
631 
632     /**
633      * Formats the specified <code>double</code> and appends the resulting text to the <code>StringBuffer</code>
634      * argument; the number of significative digits is deduced from the specified precision. All digits at least as
635      * significant as the specified precision are represented. For example:
636      * <ul>
637      * <li><code>format(5.6, 0.01, sb)</code> appends <code>"5.60"</code></li>
638      * <li><code>format(5.6, 0.1, sb)</code> appends <code>"5.6"</code></li>
639      * <li><code>format(5.6, 1, sb)</code> appends <code>"6"</code></li>
640      * </ul>
641      * If the precision is <code>0.0</code>, the precision is assumed to be the intrinsic <code>double</code> precision
642      * (64 bits IEEE 754 format); no formatting is performed, all significant digits are displayed and trailing zeros
643      * are removed.
644      *
645      * @param d the <code>double</code> number.
646      * @param precision the maximum weight of the last digit represented.
647      * @param sb the <code>StringBuffer</code> to append.
648      * @return the specified <code>StringBuffer</code> object.
649      * @throws IllegalArgumentException if the specified precision is negative or would result in too many digits (19+).
650      */
651     public static StringBuffer format(double d, double precision, StringBuffer sb) {
652         // Adjusts precision.
653         boolean precisionOnLastDigit = false;
654         if (precision > 0.0) {
655             precisionOnLastDigit = true;
656         } else if (precision == 0.0) {
657             if (d != 0.0) {
658                 precision = Math.max(Math.abs(d * DOUBLE_RELATIVE_ERROR), Double.MIN_VALUE);
659             } else {
660                 return sb.append("0.0"); // Exact zero.
661             }
662         } else if (precision < 0.0) { // Not NaN
663             throw new IllegalArgumentException("precision: Negative values not allowed");
664         }
665         return format(d, precision, precisionOnLastDigit, sb);
666     }
667 
668     /**
669      * Formats the specified <code>double</code> and appends the resulting text to the <code>StringBuffer</code>
670      * argument; the number of significative digits is deduced from the specified precision.
671      *
672      * @param d the <code>double</code> number.
673      * @param precision the maximum weight of the last digit represented.
674      * @param precisionOnLastDigit indicates if the number of digits is deduced from the specified precision.
675      * @param sb the <code>StringBuffer</code> to append.
676      * @return the specified <code>StringBuffer</code> object.
677      */
678     private static StringBuffer format(double d, double precision, boolean precisionOnLastDigit, StringBuffer sb) {
679         // Special cases.
680         if (Double.isNaN(d)) {
681             return sb.append("NaN");
682         } else if (Double.isInfinite(d)) {
683             return (d >= 0) ? sb.append("Infinity") : sb.append("-Infinity");
684         }
685         if (d < 0) {
686             d = -d;
687             sb.append('-');
688         }
689 
690         // Formats decimal part.
691         int rank = (int) Math.floor(Math.log(precision) / LOG_10);
692         double digitValue = multE(d, -rank);
693         if (digitValue >= Long.MAX_VALUE) {
694             throw new IllegalArgumentException("Specified precision would result in too many digits");
695         }
696         int digitStart = sb.length();
697         format(Math.round(digitValue), sb);
698         int digitLength = sb.length() - digitStart;
699         int dotPos = digitLength + rank;
700         boolean useScientificNotation = false;
701 
702         // Inserts dot ('.')
703         if ((dotPos <= -LEADING_ZEROS.length) || (dotPos > digitLength)) {
704             // Scientific notation has to be used ("x.xxxEyy").
705             sb.insert(digitStart + 1, '.');
706             useScientificNotation = true;
707         } else if (dotPos > 0) {
708             // Dot within the string ("xxxx.xxxxx").
709             sb.insert(digitStart + dotPos, '.');
710         } else {
711             // Leading zeros ("0.xxxxx").
712             sb.insert(digitStart, LEADING_ZEROS[-dotPos]);
713         }
714 
715         // Removes trailing zeros.
716         if (!precisionOnLastDigit) {
717             int newLength = sb.length();
718             do {
719                 newLength--;
720             } while (sb.charAt(newLength) == '0');
721             sb.setLength(newLength + 1);
722         }
723 
724         // Avoids trailing '.'
725         if (sb.charAt(sb.length() - 1) == '.') {
726             if (precisionOnLastDigit) {
727                 sb.setLength(sb.length() - 1); // Prefers "xxx" to "xxx."
728             } else {
729                 sb.append('0'); // Prefer "xxx.0" to "xxx."
730             }
731         }
732 
733         // Writes exponent.
734         if (useScientificNotation) {
735             sb.append('E');
736             format(dotPos - 1, sb);
737         }
738 
739         return sb;
740     }
741 
742     private static final double LOG_10 = Math.log(10);
743 
744     private static final float FLOAT_RELATIVE_ERROR = (float) Math.pow(2, -24);
745 
746     private static final double DOUBLE_RELATIVE_ERROR = Math.pow(2, -53);
747 
748     private static String[] LEADING_ZEROS = {"0.", "0.0", "0.00"};
749 
750     /**
751      * Returns the product of the specified value with <code>10</code> raised at the specified power exponent.
752      *
753      * @param value the value.
754      * @param E the exponent.
755      * @return <code>value * 10^E</code>
756      */
757     private static final double multE(double value, int E) {
758         if (E >= 0) {
759             if (E <= 308) {
760                 // Max: 1.7976931348623157E+308
761                 return value * DOUBLE_POW_10[E];
762             } else {
763                 value *= 1E21; // Exact multiplicand.
764                 E = Math.min(308, E - 21);
765                 return value * DOUBLE_POW_10[E];
766             }
767         } else {
768             if (E >= -308) {
769                 return value / DOUBLE_POW_10[-E];
770             } else {
771                 // Min: 4.9E-324
772                 value /= 1E21; // Exact divisor.
773                 E = Math.max(-308, E + 21);
774                 return value / DOUBLE_POW_10[-E];
775             }
776         }
777     }
778 
779     // Note: Approximation for exponents > 21. This may introduce round-off
780     // errors (e.g. 1E23 represented as "9.999999999999999E22").
781     private static final double[] DOUBLE_POW_10 = new double[] {
782         1E000, 1E001, 1E002, 1E003, 1E004, 1E005, 1E006, 1E007, 1E008, 1E009, 1E010, 1E011, 1E012, 1E013, 1E014, 1E015,
783         1E016, 1E017, 1E018, 1E019, 1E020, 1E021, 1E022, 1E023, 1E024, 1E025, 1E026, 1E027, 1E028, 1E029, 1E030, 1E031,
784         1E032, 1E033, 1E034, 1E035, 1E036, 1E037, 1E038, 1E039, 1E040, 1E041, 1E042, 1E043, 1E044, 1E045, 1E046, 1E047,
785         1E048, 1E049, 1E050, 1E051, 1E052, 1E053, 1E054, 1E055, 1E056, 1E057, 1E058, 1E059, 1E060, 1E061, 1E062, 1E063,
786         1E064, 1E065, 1E066, 1E067, 1E068, 1E069, 1E070, 1E071, 1E072, 1E073, 1E074, 1E075, 1E076, 1E077, 1E078, 1E079,
787         1E080, 1E081, 1E082, 1E083, 1E084, 1E085, 1E086, 1E087, 1E088, 1E089, 1E090, 1E091, 1E092, 1E093, 1E094, 1E095,
788         1E096, 1E097, 1E098, 1E099, 1E100, 1E101, 1E102, 1E103, 1E104, 1E105, 1E106, 1E107, 1E108, 1E109, 1E110, 1E111,
789         1E112, 1E113, 1E114, 1E115, 1E116, 1E117, 1E118, 1E119, 1E120, 1E121, 1E122, 1E123, 1E124, 1E125, 1E126, 1E127,
790         1E128, 1E129, 1E130, 1E131, 1E132, 1E133, 1E134, 1E135, 1E136, 1E137, 1E138, 1E139, 1E140, 1E141, 1E142, 1E143,
791         1E144, 1E145, 1E146, 1E147, 1E148, 1E149, 1E150, 1E151, 1E152, 1E153, 1E154, 1E155, 1E156, 1E157, 1E158, 1E159,
792         1E160, 1E161, 1E162, 1E163, 1E164, 1E165, 1E166, 1E167, 1E168, 1E169, 1E170, 1E171, 1E172, 1E173, 1E174, 1E175,
793         1E176, 1E177, 1E178, 1E179, 1E180, 1E181, 1E182, 1E183, 1E184, 1E185, 1E186, 1E187, 1E188, 1E189, 1E190, 1E191,
794         1E192, 1E193, 1E194, 1E195, 1E196, 1E197, 1E198, 1E199, 1E200, 1E201, 1E202, 1E203, 1E204, 1E205, 1E206, 1E207,
795         1E208, 1E209, 1E210, 1E211, 1E212, 1E213, 1E214, 1E215, 1E216, 1E217, 1E218, 1E219, 1E220, 1E221, 1E222, 1E223,
796         1E224, 1E225, 1E226, 1E227, 1E228, 1E229, 1E230, 1E231, 1E232, 1E233, 1E234, 1E235, 1E236, 1E237, 1E238, 1E239,
797         1E240, 1E241, 1E242, 1E243, 1E244, 1E245, 1E246, 1E247, 1E248, 1E249, 1E250, 1E251, 1E252, 1E253, 1E254, 1E255,
798         1E256, 1E257, 1E258, 1E259, 1E260, 1E261, 1E262, 1E263, 1E264, 1E265, 1E266, 1E267, 1E268, 1E269, 1E270, 1E271,
799         1E272, 1E273, 1E274, 1E275, 1E276, 1E277, 1E278, 1E279, 1E280, 1E281, 1E282, 1E283, 1E284, 1E285, 1E286, 1E287,
800         1E288, 1E289, 1E290, 1E291, 1E292, 1E293, 1E294, 1E295, 1E296, 1E297, 1E298, 1E299, 1E300, 1E301, 1E302, 1E303,
801         1E304, 1E305, 1E306, 1E307, 1E308
802     };
803 }