// Copyright (c) Jython Developers
package org.python.core.stringlib;

import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;

import org.python.core.stringlib.InternalFormat.Spec;

/**
 * A class that provides the implementation of floating-point formatting. In a limited way, it acts
 * like a StringBuilder to which text and one or more numbers may be appended, formatted according
 * to the format specifier supplied at construction. These are ephemeral objects that are not, on
 * their own, thread safe.
 */
public class FloatFormatter extends InternalFormat.Formatter {

    /** The rounding mode dominant in the formatter. */
    static final RoundingMode ROUND_PY = RoundingMode.HALF_EVEN;

    /** Limit the size of results. */
    // No-one needs more than log(Double.MAX_VALUE) - log2(Double.MIN_VALUE) = 1383 digits.
    static final int MAX_PRECISION = 1400;

    /** If it contains no decimal point, this length is zero, and 1 otherwise. */
    private int lenPoint;
    /** The length of the fractional part, right of the decimal point. */
    private int lenFraction;
    /** The length of the exponent marker ("e"), "inf" or "nan", or zero if there isn't one. */
    private int lenMarker;
    /** The length of the exponent sign and digits or zero if there isn't one. */
    private int lenExponent;
    /** if ≥0, minimum digits to follow decimal point (where consulted) */
    private int minFracDigits;

    /**
     * Construct the formatter from a client-supplied buffer, to which the result will be appended,
     * and a specification. Sets {@link #mark} to the end of the buffer.
     *
     * @param result destination buffer
     * @param spec parsed conversion specification
     */
    public FloatFormatter(StringBuilder result, Spec spec) {
        super(result, spec);
        if (spec.alternate) {
            // Alternate form means do not trim the zero fractional digits.
            minFracDigits = -1;
        } else if (spec.type == 'r' || spec.type == Spec.NONE) {
            // These formats by default show at least one fractional digit.
            minFracDigits = 1;
        } else {
            /*
             * Every other format (if it does not ignore the setting) will by default trim off all
             * the trailing zero fractional digits.
             */
            minFracDigits = 0;
        }
    }

    /**
     * Construct the formatter from a specification, allocating a buffer internally for the result.
     *
     * @param spec parsed conversion specification
     */
    public FloatFormatter(Spec spec) {
        this(new StringBuilder(size(spec)), spec);
    }

    /**
     * Recommend a buffer size for a given specification, assuming one float is converted. This will
     * be a "right" answer for e and g-format, and for f-format with values up to 9,999,999.
     *
     * @param spec parsed conversion specification
     */
    public static int size(Spec spec) {
        // Rule of thumb used here (no right answer):
        // in e format each float occupies: (p-1) + len("+1.e+300") = p+7;
        // in f format each float occupies: p + len("1,000,000.%") = p+11;
        // or an explicit (minimum) width may be given, with one overshoot possible.
        return Math.max(spec.width + 1, spec.getPrecision(6) + 11);
    }

    /**
     * Override the default truncation behaviour for the specification originally supplied. Some
     * formats remove trailing zero digits, trimming to zero or one. Set member
     * minFracDigits, to modify this behaviour.
     *
     * @param minFracDigits if <0 prevent truncation; if ≥0 the minimum number of fractional
     *            digits; when this is zero, and all fractional digits are zero, the decimal point
     *            will also be removed.
     */
    public void setMinFracDigits(int minFracDigits) {
        this.minFracDigits = minFracDigits;
    }

    @Override
    protected void reset() {
        // Clear the variables describing the latest number in result.
        super.reset();
        lenPoint = lenFraction = lenMarker = lenExponent = 0;
    }

    @Override
    protected int[] sectionLengths() {
        return new int[] {lenSign, lenWhole, lenPoint, lenFraction, lenMarker, lenExponent};
    }

    /*
     * Re-implement the text appends so they return the right type.
     */
    @Override
    public FloatFormatter append(char c) {
        super.append(c);
        return this;
    }

    @Override
    public FloatFormatter append(CharSequence csq) {
        super.append(csq);
        return this;
    }

    @Override
    public FloatFormatter append(CharSequence csq, int start, int end) //
            throws IndexOutOfBoundsException {
        super.append(csq, start, end);
        return this;
    }

    /**
     * Format a floating-point number according to the specification represented by this
     * FloatFormatter.
     *
     * @param value to convert
     * @return this object
     */
    public FloatFormatter format(double value) {
        return format(value, null);
    }

    /**
     * Format a floating-point number according to the specification represented by this
     * FloatFormatter. The conversion type, precision, and flags for grouping or
     * percentage are dealt with here. At the point this is used, we know the {@link #spec} is one
     * of the floating-point types. This entry point allows explicit control of the prefix of
     * positive numbers, overriding defaults for the format type.
     *
     * @param value to convert
     * @param positivePrefix to use before positive values (e.g. "+") or null to default to ""
     * @return this object
     */
    @SuppressWarnings("fallthrough")
    public FloatFormatter format(double value, String positivePrefix) {

        // Puts all instance variables back to their starting defaults, and start = result.length().
        setStart();

        // Precision defaults to 6 (or 12 for none-format)
        int precision = spec.getPrecision(Spec.specified(spec.type) ? 6 : 12);

        // Guard against excessive result precision
        // XXX Possibly better raised before result is allocated/sized.
        if (precision > MAX_PRECISION) {
            throw precisionTooLarge("float");
        }

        /*
         * By default, the prefix of a positive number is "", but the format specifier may override
         * it, and the built-in type complex needs to override the format.
         */
        char sign = spec.sign;
        if (positivePrefix == null && Spec.specified(sign) && sign != '-') {
            positivePrefix = Character.toString(sign);
        }

        // Different process for each format type, ignoring case for now.
        switch (Character.toLowerCase(spec.type)) {
            case 'e':
                // Exponential case: 1.23e-45
                format_e(value, positivePrefix, precision);
                break;

            case 'f':
                // Fixed case: 123.45
                format_f(value, positivePrefix, precision);
                break;

            case 'n':
                // Locale-sensitive version of g-format should be here. (Désolé de vous decevoir.)
                // XXX Set a variable here to signal localisation in/after groupDigits?
            case 'g':
                // General format: fixed or exponential according to value.
                format_g(value, positivePrefix, precision, 0);
                break;

            case Spec.NONE:
                // None format like g-format but goes exponential at precision-1
                format_g(value, positivePrefix, precision, -1);
                break;

            case 'r':
                // For float.__repr__, very special case, breaks all the rules.
                format_r(value, positivePrefix);
                break;

            case '%':
                // Multiplies by 100 and displays in f-format, followed by a percent sign.
                format_f(100. * value, positivePrefix, precision);
                result.append('%');
                break;

            default:
                // Should never get here, since this was checked in PyFloat.
                throw unknownFormat(spec.type, "float");
        }

        // If the format type is an upper-case letter, convert the result to upper case.
        if (Character.isUpperCase(spec.type)) {
            uppercase();
        }

        // If required to, group the whole-part digits.
        if (spec.grouping) {
            groupDigits(3, ',');
        }

        return this;
    }

    /**
     * Convert just the letters in the representation of the current number (in {@link #result}) to
     * upper case. (That's the exponent marker or the "inf" or "nan".)
     */
    @Override
    protected void uppercase() {
        int letters = indexOfMarker();
        int end = letters + lenMarker;
        for (int i = letters; i < end; i++) {
            char c = result.charAt(i);
            result.setCharAt(i, Character.toUpperCase(c));
        }
    }

    /**
     * Common code to deal with the sign, and the special cases "0", "-0", "nan, "inf", or "-inf".
     * If the method returns false, we have started a non-zero number and the sign is
     * already in {@link #result}. The client need then only encode abs(value). If the method
     * returns true, and {@link #lenMarker}==0, the value was "0" or "-0": the caller
     * may have to zero-extend this, and/or add an exponent, to match the requested format. If the
     * method returns true, and {@link #lenMarker}>0, the method has placed "nan, "inf"
     * in the {@link #result} buffer (preceded by a sign if necessary).
     *
     * @param value to convert
     * @return true if the value was one of "0", "-0", "nan, "inf", or "-inf".
     * @param positivePrefix to use before positive values (e.g. "+") or null to default to ""
     */
    private boolean signAndSpecialNumber(double value, String positivePrefix) {

        // This is easiest via the raw bits
        long bits = Double.doubleToRawLongBits(value);

        // NaN is always positive
        if (Double.isNaN(value)) {
            bits &= ~SIGN_MASK;
        }

        if ((bits & SIGN_MASK) != 0) {
            // Negative: encode a minus sign and strip it off bits
            result.append('-');
            lenSign = 1;
            bits &= ~SIGN_MASK;

        } else if (positivePrefix != null) {
            // Positive, and a prefix is required. Note CPython 2.7 produces "+nan", " nan".
            result.append(positivePrefix);
            lenSign = positivePrefix.length();
        }

        if (bits == 0L) {
            // All zero means it's zero. (It may have been negative, producing -0.)
            result.append('0');
            lenWhole = 1;
            return true;

        } else if ((bits & EXP_MASK) == EXP_MASK) {
            // This is characteristic of NaN or Infinity.
            result.append(((bits & ~EXP_MASK) == 0L) ? "inf" : "nan");
            lenMarker = 3;
            return true;

        } else {
            return false;
        }
    }

    private static final long SIGN_MASK = 0x8000000000000000L;
    private static final long EXP_MASK = 0x7ff0000000000000L;

    /**
     * The e-format helper function of {@link #format(double, String)} that uses Java's
     * {@link BigDecimal} to provide conversion and rounding. The converted number is appended to
     * the {@link #result} buffer, and {@link #start} will be set to the index of its first
     * character.
     *
     * @param value to convert
     * @param positivePrefix to use before positive values (e.g. "+") or null to default to ""
     * @param precision precision (maximum number of fractional digits)
     */
    private void format_e(double value, String positivePrefix, int precision) {

        // Exponent (default value is for 0.0 and -0.0)
        int exp = 0;

        if (!signAndSpecialNumber(value, positivePrefix)) {
            // Convert abs(value) to decimal with p+1 digits of accuracy.
            MathContext mc = new MathContext(precision + 1, ROUND_PY);
            BigDecimal vv = new BigDecimal(Math.abs(value), mc);

            // Take explicit control in order to get exponential notation out of BigDecimal.
            String digits = vv.unscaledValue().toString();
            int digitCount = digits.length();
            result.append(digits.charAt(0));
            lenWhole = 1;
            if (digitCount > 1) {
                // There is a fractional part
                result.append('.').append(digits.substring(1));
                lenPoint = 1;
                lenFraction = digitCount - 1;
            }
            exp = lenFraction - vv.scale();
        }

        // If the result is not already complete, add point and zeros as necessary, and exponent.
        if (lenMarker == 0) {
            ensurePointAndTrailingZeros(precision);
            appendExponent(exp);
        }
    }

    /**
     * The f-format inner helper function of {@link #format(double, String)} that uses Java's
     * {@link BigDecimal} to provide conversion and rounding. The converted number is appended to
     * the {@link #result} buffer, and {@link #start} will be set to the index of its first
     * character.
     *
     * @param value to convert
     * @param positivePrefix to use before positive values (e.g. "+") or null to default to ""
     * @param precision precision (maximum number of fractional digits)
     */
    private void format_f(double value, String positivePrefix, int precision) {

        if (!signAndSpecialNumber(value, positivePrefix)) {
            // Convert value to decimal exactly. (This can be very long.)
            BigDecimal vLong = new BigDecimal(Math.abs(value));

            // Truncate to the defined number of places to the right of the decimal point).
            BigDecimal vv = vLong.setScale(precision, ROUND_PY);

            // When converted to text, the number of fractional digits is exactly the scale we set.
            String raw = vv.toPlainString();
            result.append(raw);
            if ((lenFraction = vv.scale()) > 0) {
                // There is a decimal point and some digits following
                lenWhole = result.length() - (start + lenSign + (lenPoint = 1) + lenFraction);
            } else {
                // There are no fractional digits and so no decimal point
                lenWhole = result.length() - (start + lenSign);
            }
        }

        // Finally, ensure we have all the fractional digits we should.
        if (lenMarker == 0) {
            ensurePointAndTrailingZeros(precision);
        }
    }

    /**
     * Append a decimal point and trailing fractional zeros if necessary for 'e' and 'f' format.
     * This should not be called if the result is not numeric ("inf" for example). This method deals
     * with the following complexities: on return there will be at least the number of fractional
     * digits specified in the argument n, and at least {@link #minFracDigits};
     * further, if minFracDigits<0, signifying the "alternate mode" of certain
     * formats, the method will ensure there is a decimal point, even if there are no fractional
     * digits to follow.
     *
     * @param n smallest number of fractional digits on return
     */
    private void ensurePointAndTrailingZeros(int n) {

        // Set n to the number of fractional digits we should have.
        if (n < minFracDigits) {
            n = minFracDigits;
        }

        // Do we have a decimal point already?
        if (lenPoint == 0) {
            // No decimal point: add one if there will be any fractional digits or
            if (n > 0 || minFracDigits < 0) {
                // First need to add a decimal point.
                result.append('.');
                lenPoint = 1;
            }
        }

        // Do we have enough fractional digits already?
        int f = lenFraction;
        if (n > f) {
            // Make up the required number of zeros.
            for (; f < n; f++) {
                result.append('0');
            }
            lenFraction = f;
        }
    }

    /**
     * Implementation of the variants of g-format, that uses Java's {@link BigDecimal} to provide
     * conversion and rounding. These variants are g-format proper, alternate g-format (available
     * for "%#g" formatting), n-format (as g but subsequently "internationalised"), and none-format
     * (type code Spec.NONE).
     * 

* None-format is the basis of float.__str__. *

* According to the Python documentation for g-format, the precise rules are as follows: suppose * that the result formatted with presentation type 'e' and precision p-1 * would have exponent exp. Then if -4 <= exp < p, the number is formatted with * presentation type 'f' and precision p-1-exp. Otherwise, the number is * formatted with presentation type 'e' and precision p-1. In both cases * insignificant trailing zeros are removed from the significand, and the decimal point is also * removed if there are no remaining digits following it. *

* The Python documentation says none-format is the same as g-format, but the observed behaviour * differs from this, in that f-format is only used if -4 <= exp < p-1 (i.e. one * less), and at least one digit to the right of the decimal point is preserved in the f-format * (but not the e-format). That behaviour is controlled through the following arguments, with * these recommended values: * *

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Recommended values for formatting arguments
typeprecisionminFracDigitsexpThresholdAdjexpThreshold
gp00p
#gp-0p
\0p1-1p-1
__str__121-111
* * @param value to convert * @param positivePrefix to use before positive values (e.g. "+") or null to default to "" * @param precision total number of significant digits (precision 0 behaves as 1) * @param expThresholdAdj +precision = the exponent at which to resume using * exponential notation */ private void format_g(double value, String positivePrefix, int precision, int expThresholdAdj) { // Precision 0 behaves as 1 precision = Math.max(1, precision); // Use exponential notation if exponent would be bigger thatn: int expThreshold = precision + expThresholdAdj; if (signAndSpecialNumber(value, positivePrefix)) { // Finish formatting if zero result. (This is a no-op for nan or inf.) zeroHelper(precision, expThreshold); } else { // Convert abs(value) to decimal with p digits of accuracy. MathContext mc = new MathContext(precision, ROUND_PY); BigDecimal vv = new BigDecimal(Math.abs(value), mc); // This gives us the digits we need for either fixed or exponential format. String pointlessDigits = vv.unscaledValue().toString(); // If we were to complete this as e-format, the exponent would be: int exp = pointlessDigits.length() - vv.scale() - 1; if (-4 <= exp && exp < expThreshold) { // Finish the job as f-format with variable-precision p-(exp+1). appendFixed(pointlessDigits, exp, precision); } else { // Finish the job as e-format. appendExponential(pointlessDigits, exp); } } } /** * Implementation of r-format (float.__repr__) that uses Java's * {@link Double#toString(double)} to provide conversion and rounding. That method gives us * almost what we need, but not quite (sometimes it yields 18 digits): here we always round to * 17 significant digits. Much of the formatting after conversion is shared with * {@link #format_g(double, String, int, int, int)}. minFracDigits is consulted * since while float.__repr__ truncates to one digit, within * complex.__repr__ we truncate fully. * * @param value to convert * @param positivePrefix to use before positive values (e.g. "+") or null to default to "" */ private void format_r(double value, String positivePrefix) { // Characteristics of repr (precision = 17 and go exponential at 16). int precision = 17; int expThreshold = precision - 1; if (signAndSpecialNumber(value, positivePrefix)) { // Finish formatting if zero result. (This is a no-op for nan or inf.) zeroHelper(precision, expThreshold); } else { // Generate digit sequence (with no decimal point) with custom rounding. StringBuilder pointlessBuffer = new StringBuilder(20); int exp = reprDigits(Math.abs(value), precision, pointlessBuffer); if (-4 <= exp && exp < expThreshold) { // Finish the job as f-format with variable-precision p-(exp+1). appendFixed(pointlessBuffer, exp, precision); } else { // Finish the job as e-format. appendExponential(pointlessBuffer, exp); } } } /** * Common code for g-format, none-format and r-format called when the conversion yields "inf", * "nan" or zero. The method completes formatting of the zero, with the appropriate number of * decimal places or (in particular circumstances) exponential; notation. * * @param precision of conversion (number of significant digits). * @param expThreshold if zero, causes choice of exponential notation for zero. */ private void zeroHelper(int precision, int expThreshold) { if (lenMarker == 0) { // May be 0 or -0 so we still need to ... if (minFracDigits < 0) { // In "alternate format", we won't economise trailing zeros. appendPointAndTrailingZeros(precision - 1); } else if (lenFraction < minFracDigits) { // Otherwise, it should be at least the stated minimum length. appendTrailingZeros(minFracDigits); } // And just occasionally (in none-format) we go exponential even when exp = 0... if (0 >= expThreshold) { appendExponent(0); } } } /** * Common code for g-format, none-format and r-format used when the exponent is such that a * fixed-point presentation is chosen. Normally the method removes trailing digits so as to * shorten the presentation without loss of significance. This method respects the minimum * number of fractional digits (digits after the decimal point), in member * minFracDigits, which is 0 for g-format and 1 for none-format and r-format. When * minFracDigits<0 this signifies "no truncation" mode, in which trailing zeros * generated in the conversion are not removed. This supports "%#g" format. * * @param digits from converting the value at a given precision. * @param exp would be the exponent (in e-format), used to position the decimal point. * @param precision of conversion (number of significant digits). */ private void appendFixed(CharSequence digits, int exp, int precision) { // Check for "alternate format", where we won't economise trailing zeros. boolean noTruncate = (minFracDigits < 0); int digitCount = digits.length(); if (exp < 0) { // For a negative exponent, we must insert leading zeros 0.000 ... result.append("0."); lenWhole = lenPoint = 1; for (int i = -1; i > exp; --i) { result.append('0'); } // Then the generated digits (always enough to satisfy no-truncate mode). result.append(digits); lenFraction = digitCount - exp - 1; } else { // For a non-negative exponent, it's a question of placing the decimal point. int w = exp + 1; if (w < digitCount) { // There are w whole-part digits result.append(digits.subSequence(0, w)); lenWhole = w; result.append('.').append(digits.subSequence(w, digitCount)); lenPoint = 1; lenFraction = digitCount - w; } else { // All the digits are whole-part digits. result.append(digits); // Just occasionally (in r-format) we need more digits than the precision. while (digitCount < w) { result.append('0'); digitCount += 1; } lenWhole = digitCount; } if (noTruncate) { // Extend the fraction as BigDecimal will have economised on zeros. appendPointAndTrailingZeros(precision - digitCount); } } // Finally, ensure we have all and only the fractional digits we should. if (!noTruncate) { if (lenFraction < minFracDigits) { // Otherwise, it should be at least the stated minimum length. appendTrailingZeros(minFracDigits); } else { // And no more removeTrailingZeros(minFracDigits); } } } /** * Common code for g-format, none-format and r-format used when the exponent is such that an * exponential presentation is chosen. Normally the method removes trailing digits so as to * shorten the presentation without loss of significance. Although no minimum number of * fractional digits is enforced in the exponential presentation, when * minFracDigits<0 this signifies "no truncation" mode, in which trailing zeros * generated in the conversion are not removed. This supports "%#g" format. * * @param digits from converting the value at a given precision. * @param exp would be the exponent (in e-format), used to position the decimal point. */ private void appendExponential(CharSequence digits, int exp) { // The whole-part is the first digit. result.append(digits.charAt(0)); lenWhole = 1; // And the rest of the digits form the fractional part int digitCount = digits.length(); result.append('.').append(digits.subSequence(1, digitCount)); lenPoint = 1; lenFraction = digitCount - 1; // In no-truncate mode, the fraction is full precision. Otherwise trim it. if (minFracDigits >= 0) { // Note positive minFracDigits only applies to fixed formats. removeTrailingZeros(0); } // Finally, append the exponent as e+nn. appendExponent(exp); } /** * Convert a double to digits and an exponent for use in float.__repr__ (or * r-format). This method takes advantage of (or assumes) a close correspondence between * {@link Double#toString(double)} and Python float.__repr__. The correspondence * appears to be exact, insofar as the Java method produces the minimal non-zero digit string. * It mostly chooses the same number of digits (and the same digits) as the CPython repr, but in * a few cases Double.toString produces more digits. This method truncates to the * number maxDigits, which in practice is always 17. * * @param value to convert * @param maxDigits maximum number of digits to return in buf. * @param buf for digits of result (recommend size be 20) * @return the exponent */ private static int reprDigits(double value, int maxDigits, StringBuilder buf) { // Most of the work is done by Double. String s = Double.toString(value); // Variables for scanning the string int p = 0, end = s.length(), first = 0, point = end, exp; char c = 0; boolean allZero = true; // Scan whole part and fractional part digits while (p < end) { c = s.charAt(p++); if (Character.isDigit(c)) { if (allZero) { if (c != '0') { // This is the first non-zero digit. buf.append(c); allZero = false; // p is one *after* the first non-zero digit. first = p; } // Only seen zeros so far: do nothing. } else { // We've started, so every digit counts. buf.append(c); } } else if (c == '.') { // We remember this location (one *after* '.') to calculate the exponent later. point = p; } else { // Something after the mantissa. (c=='E' we hope.) break; } } // Possibly followed by an exponent. p has already advanced past the 'E'. if (p < end && c == 'E') { // If there is an exponent, the mantissa must be in standard form: m.mmmm assert point == first + 1; exp = Integer.parseInt(s.substring(p)); } else { // Exponent is based on relationship of decimal point and first non-zero digit. exp = point - first - 1; // But that's only correct when the point is to the right (or absent). if (exp < 0) { // The point is to the left of the first digit exp += 1; // = -(first-point) } } /* * XXX This still does not round in all the cases it could. I think Java stops generating * digits when the residual is <= ulp/2. This is to neglect the possibility that the extra * ulp/2 (before it becomes a different double) could take us to a rounder numeral. To fix * this, we could express ulp/2 as digits in the same scale as those in the buffer, and * consider adding them. But Java's behaviour here is probably a manifestation of bug * JDK-4511638. */ // Sometimes the result is more digits than we want for repr. if (buf.length() > maxDigits) { // Chop the trailing digits, remembering the most significant lost digit. int d = buf.charAt(maxDigits); buf.setLength(maxDigits); // We round half up. Not absolutely correct since Double has already rounded. if (d >= '5') { // Treat this as a "carry one" into the numeral buf[0:maxDigits]. for (p = maxDigits - 1; p >= 0; p--) { // Each pass of the loop does one carry from buf[p+1] to buf[p]. d = buf.charAt(p) + 1; if (d <= '9') { // Carry propagation stops here. buf.setCharAt(p, (char)d); break; } else { // 9 + 1 -> 0 carry 1. Keep looping. buf.setCharAt(p, '0'); } } if (p < 0) { /* * We fell off the bottom of the buffer with one carry still to propagate. You * may expect: buf.insert(0, '1') here, but note that every digit in * buf[0:maxDigits] is currently '0', so all we need is: */ buf.setCharAt(0, '1'); exp += 1; } } } return exp; } /** * Append the trailing fractional zeros, as required by certain formats, so that the total * number of fractional digits is no less than specified. If n<=0, the method * leaves the {@link #result} buffer unchanged. * * @param n smallest number of fractional digits on return */ private void appendTrailingZeros(int n) { int f = lenFraction; if (n > f) { if (lenPoint == 0) { // First need to add a decimal point. (Implies lenFraction=0.) result.append('.'); lenPoint = 1; } // Now make up the required number of zeros. for (; f < n; f++) { result.append('0'); } lenFraction = f; } } /** * Append the trailing fractional zeros, as required by certain formats, so that the total * number of fractional digits is no less than specified. If there is no decimal point * originally (and therefore no fractional part), the method will add a decimal point, even if * it adds no zeros. * * @param n smallest number of fractional digits on return */ private void appendPointAndTrailingZeros(int n) { if (lenPoint == 0) { // First need to add a decimal point. (Implies lenFraction=0.) result.append('.'); lenPoint = 1; } // Now make up the required number of zeros. int f; for (f = lenFraction; f < n; f++) { result.append('0'); } lenFraction = f; } /** * Remove trailing zeros from the fractional part, as required by certain formats, leaving at * least the number of fractional digits specified. If the resultant number of fractional digits * is zero, this method will also remove the trailing decimal point (if there is one). * * @param n smallest number of fractional digits on return */ private void removeTrailingZeros(int n) { if (lenPoint > 0) { // There's a decimal point at least, and there may be some fractional digits. int f = lenFraction; if (n == 0 || f > n) { int fracStart = result.length() - f; for (; f > n; --f) { if (result.charAt(fracStart - 1 + f) != '0') { // Keeping this one as it isn't a zero break; } } // f is now the number of fractional digits we wish to retain. if (f == 0) { // We will be stripping all the fractional digits. Take the decimal point too. lenPoint = lenFraction = 0; f = -1; } else { lenFraction = f; } // Snip the characters we are going to remove (if any). if (fracStart + f < result.length()) { result.setLength(fracStart + f); } } } } /** * Append the current value of {@code exp} in the format "e{:+02d}" (for example * e+05, e-10, e+308 , etc.). * * @param exp exponent value to append */ private void appendExponent(int exp) { int marker = result.length(); String e; // Deal with sign and leading-zero convention by explicit tests. if (exp < 0) { e = (exp <= -10) ? "e-" : "e-0"; exp = -exp; } else { e = (exp < 10) ? "e+0" : "e+"; } result.append(e).append(exp); lenMarker = 1; lenExponent = result.length() - marker - 1; } /** * Return the index in {@link #result} of the first letter. This is a helper for * {@link #uppercase()} and {@link #getExponent()} */ private int indexOfMarker() { return start + lenSign + lenWhole + lenPoint + lenFraction; } }

Ads help maintain this website.