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 }