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