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    *      http://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  /**
20   * Internal class representing a decimal value parsed into separate components. Each number
21   * is represented with
22   * <ul>
23   *  <li>a boolean flag for the sign,</li>
24   *  <li> a sequence of the digits {@code 0 - 10} representing an unsigned integer with leading and trailing zeros
25   *      removed, and</li>
26   *  <li>an exponent value that when applied to the base 10 digits produces a floating point value with the
27   *      correct magnitude.</li>
28   * </ul>
29   * <p><strong>Examples</strong></p>
30   * <table>
31   *  <tr><th>Double</th><th>Negative</th><th>Digits</th><th>Exponent</th></tr>
32   *  <tr><td>0.0</td><td>false</td><td>[0]</td><td>0</td></tr>
33   *  <tr><td>1.2</td><td>false</td><td>[1, 2]</td><td>-1</td></tr>
34   *  <tr><td>-0.00971</td><td>true</td><td>[9, 7, 1]</td><td>-5</td></tr>
35   *  <tr><td>56300</td><td>true</td><td>[5, 6, 3]</td><td>2</td></tr>
36   * </table>
37   */
38  final class ParsedDecimal {
39  
40      /**
41       * Interface containing values used during string formatting.
42       */
43      interface FormatOptions {
44  
45          /**
46           * Gets the decimal separator character.
47           * @return decimal separator character
48           */
49          char getDecimalSeparator();
50  
51          /**
52           * Gets an array containing the localized digit characters 0-9 in that order.
53           * This string <em>must</em> be non-null and have a length of 10.
54           * @return array containing the digit characters 0-9
55           */
56          char[] getDigits();
57  
58          /**
59           * Gets the exponent separator as an array of characters.
60           * @return exponent separator as an array of characters
61           */
62          char[] getExponentSeparatorChars();
63  
64          /**
65           * Gets the character used to separate thousands groupings.
66           * @return character used to separate thousands groupings
67           */
68          char getGroupingSeparator();
69  
70          /**
71           * Gets the minus sign character.
72           * @return minus sign character
73           */
74          char getMinusSign();
75  
76          /**
77           * Return {@code true} if exponent values should always be included in
78           * formatted output, even if the value is zero.
79           * @return {@code true} if exponent values should always be included
80           */
81          boolean isAlwaysIncludeExponent();
82  
83          /**
84           * Return {@code true} if thousands should be grouped.
85           * @return {@code true} if thousand should be grouped
86           */
87          boolean isGroupThousands();
88  
89          /**
90           * Return {@code true} if fraction placeholders (e.g., {@code ".0"} in {@code "1.0"})
91           * should be included.
92           * @return {@code true} if fraction placeholders should be included
93           */
94          boolean isIncludeFractionPlaceholder();
95  
96          /**
97           * Return {@code true} if the string zero should be prefixed with the minus sign
98           * for negative zero values.
99           * @return {@code true} if the minus zero string should be allowed
100          */
101         boolean isSignedZero();
102     }
103 
104     /** Minus sign character. */
105     private static final char MINUS_CHAR = '-';
106 
107     /** Decimal separator character. */
108     private static final char DECIMAL_SEP_CHAR = '.';
109 
110     /** Exponent character. */
111     private static final char EXPONENT_CHAR = 'E';
112 
113     /** Zero digit character. */
114     private static final char ZERO_CHAR = '0';
115 
116     /** Number of characters in thousands groupings. */
117     private static final int THOUSANDS_GROUP_SIZE = 3;
118 
119     /** Radix for decimal numbers. */
120     private static final int DECIMAL_RADIX = 10;
121 
122     /** Center value used when rounding. */
123     private static final int ROUND_CENTER = DECIMAL_RADIX / 2;
124 
125     /** Number that exponents in engineering format must be a multiple of. */
126     private static final int ENG_EXPONENT_MOD = 3;
127 
128     /**
129      * Gets the numeric value of the given digit character. No validation of the
130      * character type is performed.
131      * @param ch digit character
132      * @return numeric value of the digit character, ex: '1' = 1
133      */
134     private static int digitValue(final char ch) {
135         return ch - ZERO_CHAR;
136     }
137 
138     /**
139      * Constructs a new instance from the given double value.
140      * @param d double value
141      * @return a new instance containing the parsed components of the given double value
142      * @throws IllegalArgumentException if {@code d} is {@code NaN} or infinite
143      */
144     public static ParsedDecimal from(final double d) {
145         if (!Double.isFinite(d)) {
146             throw new IllegalArgumentException("Double is not finite");
147         }
148 
149         // Get the canonical string representation of the double value and parse
150         // it to extract the components of the decimal value. From the documentation
151         // of Double.toString() and the fact that d is finite, we are guaranteed the
152         // following:
153         // - the string will not be empty
154         // - it will contain exactly one decimal point character
155         // - all digit characters are in the ASCII range
156         final char[] strChars = Double.toString(d).toCharArray();
157 
158         final boolean negative = strChars[0] == MINUS_CHAR;
159         final int digitStartIdx = negative ? 1 : 0;
160 
161         final int[] digits = new int[strChars.length - digitStartIdx - 1];
162 
163         boolean foundDecimalPoint = false;
164         int digitCount = 0;
165         int significantDigitCount = 0;
166         int decimalPos = 0;
167 
168         int i;
169         for (i = digitStartIdx; i < strChars.length; ++i) {
170             final char ch = strChars[i];
171 
172             if (ch == DECIMAL_SEP_CHAR) {
173                 foundDecimalPoint = true;
174                 decimalPos = digitCount;
175             } else if (ch == EXPONENT_CHAR) {
176                 // no more mantissa digits
177                 break;
178             } else if (ch != ZERO_CHAR || digitCount > 0) {
179                 // this is either the first non-zero digit or one after it
180                 final int val = digitValue(ch);
181                 digits[digitCount++] = val;
182 
183                 if (val > 0) {
184                     significantDigitCount = digitCount;
185                 }
186             } else if (foundDecimalPoint) {
187                 // leading zero in a fraction; adjust the decimal position
188                 --decimalPos;
189             }
190         }
191 
192         if (digitCount > 0) {
193             // determine the exponent
194             final int explicitExponent = i < strChars.length
195                     ? parseExponent(strChars, i + 1)
196                     : 0;
197             final int exponent = explicitExponent + decimalPos - significantDigitCount;
198 
199             return new ParsedDecimal(negative, digits, significantDigitCount, exponent);
200         }
201 
202         // no non-zero digits, so value is zero
203         return new ParsedDecimal(negative, new int[] {0}, 1, 0);
204     }
205 
206     /**
207      * Parses a double exponent value from {@code chars}, starting at the {@code start}
208      * index and continuing through the end of the array.
209      * @param chars character array to parse a double exponent value from
210      * @param start start index
211      * @return parsed exponent value
212      */
213     private static int parseExponent(final char[] chars, final int start) {
214         int i = start;
215         final boolean neg = chars[i] == MINUS_CHAR;
216         if (neg) {
217             ++i;
218         }
219 
220         int exp = 0;
221         for (; i < chars.length; ++i) {
222             exp = exp * DECIMAL_RADIX + digitValue(chars[i]);
223         }
224 
225         return neg ? -exp : exp;
226     }
227 
228     /** True if the value is negative. */
229     final boolean negative;
230 
231     /** Array containing the significant decimal digits for the value. */
232     final int[] digits;
233 
234     /** Number of digits used in the digits array; not necessarily equal to the length. */
235     int digitCount;
236 
237     /** Exponent for the value. */
238     int exponent;
239 
240     /** Output buffer for use in creating string representations. */
241     private char[] outputChars;
242 
243     /** Output buffer index. */
244     private int outputIdx;
245 
246     /**
247      * Constructs a new instance from its parts.
248      * @param negative {@code true} if the value is negative
249      * @param digits array containing significant digits
250      * @param digitCount number of digits used from the {@code digits} array
251      * @param exponent exponent value
252      */
253     private ParsedDecimal(final boolean negative, final int[] digits, final int digitCount,
254             final int exponent) {
255         this.negative = negative;
256         this.digits = digits;
257         this.digitCount = digitCount;
258         this.exponent = exponent;
259     }
260 
261     /**
262      * Appends the given character to the output buffer.
263      * @param ch character to append
264      */
265     private void append(final char ch) {
266         outputChars[outputIdx++] = ch;
267     }
268 
269     /**
270      * Appends the given character array directly to the output buffer.
271      * @param chars characters to append
272      */
273     private void append(final char[] chars) {
274         for (final char c : chars) {
275             append(c);
276         }
277     }
278 
279     /**
280      * Appends the fractional component of the number to the current output buffer.
281      * @param zeroCount number of zeros to add after the decimal point and before the
282      *      first significant digit
283      * @param startIdx significant digit start index
284      * @param opts format options
285      */
286     private void appendFraction(final int zeroCount, final int startIdx, final FormatOptions opts) {
287         final char[] localizedDigits = opts.getDigits();
288         final char localizedZero = localizedDigits[0];
289 
290         if (startIdx < digitCount) {
291             append(opts.getDecimalSeparator());
292 
293             // add the zero prefix
294             for (int i = 0; i < zeroCount; ++i) {
295                 append(localizedZero);
296             }
297 
298             // add the fraction digits
299             for (int i = startIdx; i < digitCount; ++i) {
300                 appendLocalizedDigit(digits[i], localizedDigits);
301             }
302         } else if (opts.isIncludeFractionPlaceholder()) {
303             append(opts.getDecimalSeparator());
304             append(localizedZero);
305         }
306     }
307 
308     /**
309      * Appends the localized representation of the digit {@code n} to the output buffer.
310      * @param n digit to append
311      * @param digitChars character array containing localized versions of the digits {@code 0-9}
312      *      in that order
313      */
314     private void appendLocalizedDigit(final int n, final char[] digitChars) {
315         append(digitChars[n]);
316     }
317 
318     /**
319      * Appends the whole number portion of this value to the output buffer. No thousands
320      * separators are added.
321      * @param wholeCount total number of digits required to the left of the decimal point
322      * @param opts format options
323      * @return number of digits from {@code digits} appended to the output buffer
324      * @see #appendWholeGrouped(int, FormatOptions)
325      */
326     private int appendWhole(final int wholeCount, final FormatOptions opts) {
327         if (shouldIncludeMinus(opts)) {
328             append(opts.getMinusSign());
329         }
330 
331         final char[] localizedDigits = opts.getDigits();
332         final char localizedZero = localizedDigits[0];
333 
334         final int significantDigitCount = Math.max(0, Math.min(wholeCount, digitCount));
335 
336         if (significantDigitCount > 0) {
337             int i;
338             for (i = 0; i < significantDigitCount; ++i) {
339                 appendLocalizedDigit(digits[i], localizedDigits);
340             }
341 
342             for (; i < wholeCount; ++i) {
343                 append(localizedZero);
344             }
345         } else {
346             append(localizedZero);
347         }
348 
349         return significantDigitCount;
350     }
351 
352     /**
353      * Appends the whole number portion of this value to the output buffer, adding thousands
354      * separators as needed.
355      * @param wholeCount total number of digits required to the right of the decimal point
356      * @param opts format options
357      * @return number of digits from {@code digits} appended to the output buffer
358      * @see #appendWhole(int, FormatOptions)
359      */
360     private int appendWholeGrouped(final int wholeCount, final FormatOptions opts) {
361         if (shouldIncludeMinus(opts)) {
362             append(opts.getMinusSign());
363         }
364 
365         final char[] localizedDigits = opts.getDigits();
366         final char localizedZero = localizedDigits[0];
367         final char groupingChar = opts.getGroupingSeparator();
368 
369         final int appendCount = Math.max(0, Math.min(wholeCount, digitCount));
370 
371         if (appendCount > 0) {
372             int i;
373             int pos = wholeCount;
374             for (i = 0; i < appendCount; ++i, --pos) {
375                 appendLocalizedDigit(digits[i], localizedDigits);
376                 if (requiresGroupingSeparatorAfterPosition(pos)) {
377                     append(groupingChar);
378                 }
379             }
380 
381             for (; i < wholeCount; ++i, --pos) {
382                 append(localizedZero);
383                 if (requiresGroupingSeparatorAfterPosition(pos)) {
384                     append(groupingChar);
385                 }
386             }
387         } else {
388             append(localizedZero);
389         }
390 
391         return appendCount;
392     }
393 
394     /**
395      * Gets the number of characters required for the digit portion of a string representation of
396      * this value. This excludes any exponent or thousands groupings characters.
397      * @param decimalPos decimal point position relative to the {@code digits} array
398      * @param opts format options
399      * @return number of characters required for the digit portion of a string representation of
400      *      this value
401      */
402     private int getDigitStringSize(final int decimalPos, final FormatOptions opts) {
403         int size = digitCount;
404         if (shouldIncludeMinus(opts)) {
405             ++size;
406         }
407         if (decimalPos < 1) {
408             // no whole component;
409             // add decimal point and leading zeros
410             size += 2 + Math.abs(decimalPos);
411         } else if (decimalPos >= digitCount) {
412             // no fraction component;
413             // add trailing zeros
414             size += decimalPos - digitCount;
415             if (opts.isIncludeFractionPlaceholder()) {
416                 size += 2;
417             }
418         } else {
419             // whole and fraction components;
420             // add decimal point
421             size += 1;
422         }
423 
424         return size;
425     }
426 
427     /**
428      * Gets the exponent value. This exponent produces a floating point value with the
429      * correct magnitude when applied to the internal unsigned integer.
430      * @return exponent value
431      */
432     public int getExponent() {
433         return exponent;
434     }
435 
436     /**
437      * Gets the number of characters required to create a plain format representation
438      * of this value.
439      * @param decimalPos decimal position relative to the {@code digits} array
440      * @param opts format options
441      * @return number of characters in the plain string representation of this value,
442      *      created using the given parameters
443      */
444     private int getPlainStringSize(final int decimalPos, final FormatOptions opts) {
445         int size = getDigitStringSize(decimalPos, opts);
446 
447         // adjust for groupings if needed
448         if (opts.isGroupThousands() && decimalPos > 0) {
449             size += (decimalPos - 1) / THOUSANDS_GROUP_SIZE;
450         }
451 
452         return size;
453     }
454 
455     /**
456      * Gets the exponent that would be used when representing this number in scientific
457      * notation (i.e., with a single non-zero digit in front of the decimal point).
458      * @return the exponent that would be used when representing this number in scientific
459      *      notation
460      */
461     public int getScientificExponent() {
462         return digitCount + exponent - 1;
463     }
464 
465     /**
466      * Tests {@code true} if this value is equal to zero. The sign field is ignored,
467      * meaning that this method will return {@code true} for both {@code +0} and {@code -0}.
468      * @return {@code true} if the value is equal to zero
469      */
470     boolean isZero() {
471         return digits[0] == 0;
472     }
473 
474     /**
475      * Ensures that this instance has <em>at most</em> the given number of significant digits
476      * (i.e. precision). If this instance already has a precision less than or equal
477      * to the argument, nothing is done. If the given precision requires a reduction in the number
478      * of digits, then the value is rounded using {@link java.math.RoundingMode#HALF_EVEN half-even rounding}.
479      * @param precision maximum number of significant digits to include
480      */
481     public void maxPrecision(final int precision) {
482         if (precision > 0 && precision < digitCount) {
483             if (shouldRoundUp(precision)) {
484                 roundUp(precision);
485             } else {
486                 truncate(precision);
487             }
488         }
489     }
490 
491     /**
492      * Gets the output buffer as a string.
493      * @return output buffer as a string
494      */
495     private String outputString() {
496         final String str = String.valueOf(outputChars);
497         outputChars = null;
498         return str;
499     }
500 
501     /**
502      * Prepares the output buffer for a string of the given size.
503      * @param size buffer size
504      */
505     private void prepareOutput(final int size) {
506         outputChars = new char[size];
507         outputIdx = 0;
508     }
509 
510     /**
511      * Returns {@code true} if a grouping separator should be added after the whole digit
512      * character at the given position.
513      * @param pos whole digit character position, with values starting at 1 and increasing
514      *      from right to left.
515      * @return {@code true} if a grouping separator should be added
516      */
517     private boolean requiresGroupingSeparatorAfterPosition(final int pos) {
518         return pos > 1 && pos % THOUSANDS_GROUP_SIZE == 1;
519     }
520 
521     /**
522      * Rounds the instance to the given decimal exponent position using
523      * {@link java.math.RoundingMode#HALF_EVEN half-even rounding}. For example, a value of {@code -2}
524      * will round the instance to the digit at the position 10<sup>-2</sup> (i.e. to the closest multiple of 0.01).
525      * @param roundExponent exponent defining the decimal place to round to
526      */
527     public void round(final int roundExponent) {
528         if (roundExponent > exponent) {
529             final int max = digitCount + exponent;
530 
531             if (roundExponent < max) {
532                 // rounding to a decimal place less than the max; set max precision
533                 maxPrecision(max - roundExponent);
534             } else if (roundExponent == max && shouldRoundUp(0)) {
535                 // rounding up directly on the max decimal place
536                 setSingleDigitValue(1, roundExponent);
537             } else {
538                 // change to zero
539                 setSingleDigitValue(0, 0);
540             }
541         }
542     }
543 
544     /**
545      * Rounds the value up to the given number of digits.
546      * @param count target number of digits; must be greater than zero and
547      *      less than the current number of digits
548      */
549     private void roundUp(final int count) {
550         int removedDigits = digitCount - count;
551         int i;
552         for (i = count - 1; i >= 0; --i) {
553             final int d = digits[i] + 1;
554 
555             if (d < DECIMAL_RADIX) {
556                 // value did not carry over; done adding
557                 digits[i] = d;
558                 break;
559             }
560             // value carried over; the current position is 0
561             // which we will ignore by shortening the digit count
562             ++removedDigits;
563         }
564 
565         if (i < 0) {
566             // all values carried over
567             setSingleDigitValue(1, exponent + removedDigits);
568         } else {
569             // values were updated in-place; just need to update the length
570             truncate(digitCount - removedDigits);
571         }
572     }
573 
574     /**
575      * Sets the value of this instance to a single digit with the given exponent.
576      * The sign of the value is retained.
577      * @param digit digit value
578      * @param newExponent new exponent value
579      */
580     private void setSingleDigitValue(final int digit, final int newExponent) {
581         digits[0] = digit;
582         digitCount = 1;
583         exponent = newExponent;
584     }
585 
586     /**
587      * Returns {@code true} if a formatted string with the given target exponent should include
588      * the exponent field.
589      * @param targetExponent exponent of the formatted result
590      * @param opts format options
591      * @return {@code true} if the formatted string should include the exponent field
592      */
593     private boolean shouldIncludeExponent(final int targetExponent, final FormatOptions opts) {
594         return targetExponent != 0 || opts.isAlwaysIncludeExponent();
595     }
596 
597     /**
598      * Returns {@code true} if formatted strings should include the minus sign, considering
599      * the value of this instance and the given format options.
600      * @param opts format options
601      * @return {@code true} if a minus sign should be included in the output
602      */
603     private boolean shouldIncludeMinus(final FormatOptions opts) {
604         return negative && (opts.isSignedZero() || !isZero());
605     }
606 
607     /**
608      * Returns {@code true} if a rounding operation for the given number of digits should
609      * round up.
610      * @param count number of digits to round to; must be greater than zero and less
611      *      than the current number of digits
612      * @return {@code true} if a rounding operation for the given number of digits should
613      *      round up
614      */
615     private boolean shouldRoundUp(final int count) {
616         // Round up in the following cases:
617         // 1. The digit after the last digit is greater than 5.
618         // 2. The digit after the last digit is 5 and there are additional (non-zero)
619         //      digits after it.
620         // 3. The digit after the last digit is 5, there are no additional digits afterward,
621         //      and the last digit is odd (half-even rounding).
622         final int digitAfterLast = digits[count];
623 
624         return digitAfterLast > ROUND_CENTER || digitAfterLast == ROUND_CENTER
625                 && (count < digitCount - 1 || digits[count - 1] % 2 != 0);
626     }
627 
628     /**
629      * Returns a string representation of this value in engineering notation. This is similar to {@link #toScientificString(FormatOptions) scientific notation}
630      * but with the exponent forced to be a multiple of 3, allowing easier alignment with SI prefixes.
631      * <p>
632      * For example:
633      * </p>
634      *
635      * <pre>
636      * 0 = "0.0"
637      * 10 = "10.0"
638      * 1e-6 = "1.0E-6"
639      * 1e11 = "100.0E9"
640      * </pre>
641      *
642      * @param opts format options
643      * @return value in engineering format
644      */
645     public String toEngineeringString(final FormatOptions opts) {
646         final int decimalPos = 1 + Math.floorMod(getScientificExponent(), ENG_EXPONENT_MOD);
647         return toScientificString(decimalPos, opts);
648     }
649 
650     /**
651      * Returns a string representation of this value with no exponent field.
652      * <p>
653      * For example:
654      * </p>
655      *
656      * <pre>
657      * 10 = "10.0"
658      * 1e-6 = "0.000001"
659      * 1e11 = "100000000000.0"
660      * </pre>
661      *
662      * @param opts format options
663      * @return value in plain format
664      */
665     public String toPlainString(final FormatOptions opts) {
666         final int decimalPos = digitCount + exponent;
667         final int fractionZeroCount = decimalPos < 1
668                 ? Math.abs(decimalPos)
669                 : 0;
670 
671         prepareOutput(getPlainStringSize(decimalPos, opts));
672 
673         final int fractionStartIdx = opts.isGroupThousands()
674                 ? appendWholeGrouped(decimalPos, opts)
675                 : appendWhole(decimalPos, opts);
676 
677         appendFraction(fractionZeroCount, fractionStartIdx, opts);
678 
679         return outputString();
680     }
681 
682     /**
683      * Returns a string representation of this value in scientific notation.
684      * <p>
685      * For example:
686      * </p>
687      *
688      * <pre>
689      * 0 = "0.0"
690      * 10 = "1.0E1"
691      * 1e-6 = "1.0E-6"
692      * 1e11 = "1.0E11"
693      * </pre>
694      *
695      * @param opts format options
696      * @return value in scientific format
697      */
698     public String toScientificString(final FormatOptions opts) {
699         return toScientificString(1, opts);
700     }
701 
702     /**
703      * Returns a string representation of the value in scientific notation using the
704      * given decimal point position.
705      * @param decimalPos decimal position relative to the {@code digits} array; this value
706      *      is expected to be greater than 0
707      * @param opts format options
708      * @return value in scientific format
709      */
710     private String toScientificString(final int decimalPos, final FormatOptions opts) {
711         final int targetExponent = digitCount + exponent - decimalPos;
712         final int absTargetExponent = Math.abs(targetExponent);
713         final boolean includeExponent = shouldIncludeExponent(targetExponent, opts);
714         final boolean negativeExponent = targetExponent < 0;
715 
716         // determine the size of the full formatted string, including the number of
717         // characters needed for the exponent digits
718         int size = getDigitStringSize(decimalPos, opts);
719         int exponentDigitCount = 0;
720         if (includeExponent) {
721             exponentDigitCount = absTargetExponent > 0
722                     ? (int) Math.floor(Math.log10(absTargetExponent)) + 1
723                     : 1;
724 
725             size += opts.getExponentSeparatorChars().length + exponentDigitCount;
726             if (negativeExponent) {
727                 ++size;
728             }
729         }
730 
731         prepareOutput(size);
732 
733         // append the portion before the exponent field
734         final int fractionStartIdx = appendWhole(decimalPos, opts);
735         appendFraction(0, fractionStartIdx, opts);
736 
737         if (includeExponent) {
738             // append the exponent field
739             append(opts.getExponentSeparatorChars());
740 
741             if (negativeExponent) {
742                 append(opts.getMinusSign());
743             }
744 
745             // append the exponent digits themselves; compute the
746             // string representation directly and add it to the output
747             // buffer to avoid the overhead of Integer.toString()
748             final char[] localizedDigits = opts.getDigits();
749             int rem = absTargetExponent;
750             for (int i = size - 1; i >= outputIdx; --i) {
751                 outputChars[i] = localizedDigits[rem % DECIMAL_RADIX];
752                 rem /= DECIMAL_RADIX;
753             }
754             outputIdx = size;
755         }
756 
757         return outputString();
758     }
759 
760     /**
761      * Truncates the value to the given number of digits.
762      * @param count number of digits; must be greater than zero and less than
763      *      the current number of digits
764      */
765     private void truncate(final int count) {
766         // trim all trailing zero digits, making sure to leave
767         // at least one digit left
768         int nonZeroCount = count;
769         for (int i = count - 1;
770                 i > 0 && digits[i] == 0;
771                 --i) {
772             --nonZeroCount;
773         }
774         exponent += digitCount - nonZeroCount;
775         digitCount = nonZeroCount;
776     }
777 }