// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.text.FieldPosition;

import com.ibm.icu.impl.StandardPlural;
import com.ibm.icu.impl.Utility;
import com.ibm.icu.text.PluralRules;
import com.ibm.icu.text.PluralRules.Operand;
import com.ibm.icu.text.UFieldPosition;

/**
 * Represents numbers and digit display properties using Binary Coded Decimal (BCD).
 *
 * @implements {@link DecimalQuantity}
 */
public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {

    /**
     * The power of ten corresponding to the least significant digit in the BCD. For example, if this
     * object represents the number "3.14", the BCD will be "0x314" and the scale will be -2.
     *
     * <p>
     * Note that in {@link java.math.BigDecimal}, the scale is defined differently: the number of digits
     * after the decimal place, which is the negative of our definition of scale.
     */
    protected int scale;

    /**
     * The number of digits in the BCD. For example, "1007" has BCD "0x1007" and precision 4. A long
     * cannot represent precisions greater than 16.
     *
     * <p>
     * This value must be re-calculated whenever the value in bcd changes by using
     * {@link #computePrecisionAndCompact()}.
     */
    protected int precision;

    /**
     * A bitmask of properties relating to the number represented by this object.
     *
     * @see #NEGATIVE_FLAG
     * @see #INFINITY_FLAG
     * @see #NAN_FLAG
     */
    protected byte flags;

    protected static final int NEGATIVE_FLAG = 1;
    protected static final int INFINITY_FLAG = 2;
    protected static final int NAN_FLAG = 4;

    // The following three fields relate to the double-to-ascii fast path algorithm.
    // When a double is given to DecimalQuantityBCD, it is converted to using a fast algorithm. The
    // fast algorithm guarantees correctness to only the first ~12 digits of the double. The process
    // of rounding the number ensures that the converted digits are correct, falling back to a slow-
    // path algorithm if required. Therefore, if a DecimalQuantity is constructed from a double, it
    // is *required* that roundToMagnitude(), roundToIncrement(), or roundToInfinity() is called. If
    // you don't round, assertions will fail in certain other methods if you try calling them.

    /**
     * The original number provided by the user and which is represented in BCD. Used when we need to
     * re-compute the BCD for an exact double representation.
     */
    protected double origDouble;

    /**
     * The change in magnitude relative to the original double. Used when we need to re-compute the BCD
     * for an exact double representation.
     */
    protected int origDelta;

    /**
     * Whether the value in the BCD comes from the double fast path without having been rounded to ensure
     * correctness
     */
    protected boolean isApproximate;

    // Positions to keep track of leading and trailing zeros.
    // lReqPos is the magnitude of the first required leading zero.
    // rReqPos is the magnitude of the last required trailing zero.
    protected int lReqPos = 0;
    protected int rReqPos = 0;

    @Override
    public void copyFrom(DecimalQuantity _other) {
        copyBcdFrom(_other);
        DecimalQuantity_AbstractBCD other = (DecimalQuantity_AbstractBCD) _other;
        lReqPos = other.lReqPos;
        rReqPos = other.rReqPos;
        scale = other.scale;
        precision = other.precision;
        flags = other.flags;
        origDouble = other.origDouble;
        origDelta = other.origDelta;
        isApproximate = other.isApproximate;
    }

    public DecimalQuantity_AbstractBCD clear() {
        lReqPos = 0;
        rReqPos = 0;
        flags = 0;
        setBcdToZero(); // sets scale, precision, hasDouble, origDouble, origDelta, and BCD data
        return this;
    }

    @Override
    public void setMinInteger(int minInt) {
        // Validation should happen outside of DecimalQuantity, e.g., in the Rounder class.
        assert minInt >= 0;

        // Special behavior: do not set minInt to be less than what is already set.
        // This is so significant digits rounding can set the integer length.
        if (minInt < lReqPos) {
            minInt = lReqPos;
        }

        // Save values into internal state
        lReqPos = minInt;
    }

    @Override
    public void setMinFraction(int minFrac) {
        // Validation should happen outside of DecimalQuantity, e.g., in the Rounder class.
        assert minFrac >= 0;

        // Save values into internal state
        // Negation is safe for minFrac/maxFrac because -Integer.MAX_VALUE > Integer.MIN_VALUE
        rReqPos = -minFrac;
    }

    @Override
    public void applyMaxInteger(int maxInt) {
        // Validation should happen outside of DecimalQuantity, e.g., in the Precision class.
        assert maxInt >= 0;

        if (precision == 0) {
            return;
        }

        if (maxInt <= scale) {
            setBcdToZero();
            return;
        }

        int magnitude = getMagnitude();
        if (maxInt <= magnitude) {
            popFromLeft(magnitude - maxInt + 1);
            compact();
        }
    }

    @Override
    public long getPositionFingerprint() {
        long fingerprint = 0;
        fingerprint ^= (lReqPos << 16);
        fingerprint ^= ((long) rReqPos << 32);
        return fingerprint;
    }

    @Override
    public void roundToIncrement(BigDecimal roundingIncrement, MathContext mathContext) {
        // Do not call this method with an increment having only a 1 or a 5 digit!
        // Use a more efficient call to either roundToMagnitude() or roundToNickel().
        // Note: The check, which is somewhat expensive, is performed in an assertion
        // to disable it in production.
        assert roundingIncrement.stripTrailingZeros().precision() != 1
                || roundingIncrement.stripTrailingZeros().unscaledValue().intValue() != 5
                || roundingIncrement.stripTrailingZeros().unscaledValue().intValue() != 1;
        BigDecimal temp = toBigDecimal();
        temp = temp.divide(roundingIncrement, 0, mathContext.getRoundingMode())
                .multiply(roundingIncrement)
                .round(mathContext);
        if (temp.signum() == 0) {
            setBcdToZero(); // keeps negative flag for -0.0
        } else {
            setToBigDecimal(temp);
        }
    }

    @Override
    public void multiplyBy(BigDecimal multiplicand) {
        if (isInfinite() || isZero() || isNaN()) {
            return;
        }
        BigDecimal temp = toBigDecimal();
        temp = temp.multiply(multiplicand);
        setToBigDecimal(temp);
    }

    @Override
    public void negate() {
      flags ^= NEGATIVE_FLAG;
    }

    @Override
    public int getMagnitude() throws ArithmeticException {
        if (precision == 0) {
            throw new ArithmeticException("Magnitude is not well-defined for zero");
        } else {
            return scale + precision - 1;
        }
    }

    @Override
    public void adjustMagnitude(int delta) {
        if (precision != 0) {
            scale = Utility.addExact(scale, delta);
            origDelta = Utility.addExact(origDelta, delta);
            // Make sure that precision + scale won't overflow, either
            Utility.addExact(scale, precision);
        }
    }

    @Override
    public StandardPlural getStandardPlural(PluralRules rules) {
        if (rules == null) {
            // Fail gracefully if the user didn't provide a PluralRules
            return StandardPlural.OTHER;
        } else {
            @SuppressWarnings("deprecation")
            String ruleString = rules.select(this);
            return StandardPlural.orOtherFromString(ruleString);
        }
    }

    @Override
    public double getPluralOperand(Operand operand) {
        // If this assertion fails, you need to call roundToInfinity() or some other rounding method.
        // See the comment at the top of this file explaining the "isApproximate" field.
        assert !isApproximate;

        switch (operand) {
        case i:
            // Invert the negative sign if necessary
            return isNegative() ? -toLong(true) : toLong(true);
        case f:
            return toFractionLong(true);
        case t:
            return toFractionLong(false);
        case v:
            return fractionCount();
        case w:
            return fractionCountWithoutTrailingZeros();
        default:
            return Math.abs(toDouble());
        }
    }

    @Override
    public void populateUFieldPosition(FieldPosition fp) {
        if (fp instanceof UFieldPosition) {
            ((UFieldPosition) fp).setFractionDigits((int) getPluralOperand(Operand.v),
                    (long) getPluralOperand(Operand.f));
        }
    }

    @Override
    public int getUpperDisplayMagnitude() {
        // If this assertion fails, you need to call roundToInfinity() or some other rounding method.
        // See the comment at the top of this file explaining the "isApproximate" field.
        assert !isApproximate;

        int magnitude = scale + precision;
        int result = (lReqPos > magnitude) ? lReqPos : magnitude;
        return result - 1;
    }

    @Override
    public int getLowerDisplayMagnitude() {
        // If this assertion fails, you need to call roundToInfinity() or some other rounding method.
        // See the comment at the top of this file explaining the "isApproximate" field.
        assert !isApproximate;

        int magnitude = scale;
        int result = (rReqPos < magnitude) ? rReqPos : magnitude;
        return result;
    }

    @Override
    public byte getDigit(int magnitude) {
        // If this assertion fails, you need to call roundToInfinity() or some other rounding method.
        // See the comment at the top of this file explaining the "isApproximate" field.
        assert !isApproximate;

        return getDigitPos(magnitude - scale);
    }

    private int fractionCount() {
        return -getLowerDisplayMagnitude();
    }

    private int fractionCountWithoutTrailingZeros() {
        return Math.max(-scale, 0);
    }

    @Override
    public boolean isNegative() {
        return (flags & NEGATIVE_FLAG) != 0;
    }

    @Override
    public int signum() {
        return isNegative() ? -1 : isZero() ? 0 : 1;
    }

    @Override
    public boolean isInfinite() {
        return (flags & INFINITY_FLAG) != 0;
    }

    @Override
    public boolean isNaN() {
        return (flags & NAN_FLAG) != 0;
    }

    @Override
    public boolean isZero() {
        return precision == 0;
    }

    public void setToInt(int n) {
        setBcdToZero();
        flags = 0;
        if (n < 0) {
            flags |= NEGATIVE_FLAG;
            n = -n;
        }
        if (n != 0) {
            _setToInt(n);
            compact();
        }
    }

    private void _setToInt(int n) {
        if (n == Integer.MIN_VALUE) {
            readLongToBcd(-(long) n);
        } else {
            readIntToBcd(n);
        }
    }

    public void setToLong(long n) {
        setBcdToZero();
        flags = 0;
        if (n < 0) {
            flags |= NEGATIVE_FLAG;
            n = -n;
        }
        if (n != 0) {
            _setToLong(n);
            compact();
        }
    }

    private void _setToLong(long n) {
        if (n == Long.MIN_VALUE) {
            readBigIntegerToBcd(BigInteger.valueOf(n).negate());
        } else if (n <= Integer.MAX_VALUE) {
            readIntToBcd((int) n);
        } else {
            readLongToBcd(n);
        }
    }

    public void setToBigInteger(BigInteger n) {
        setBcdToZero();
        flags = 0;
        if (n.signum() == -1) {
            flags |= NEGATIVE_FLAG;
            n = n.negate();
        }
        if (n.signum() != 0) {
            _setToBigInteger(n);
            compact();
        }
    }

    private void _setToBigInteger(BigInteger n) {
        if (n.bitLength() < 32) {
            readIntToBcd(n.intValue());
        } else if (n.bitLength() < 64) {
            readLongToBcd(n.longValue());
        } else {
            readBigIntegerToBcd(n);
        }
    }

    /**
     * Sets the internal BCD state to represent the value in the given double.
     *
     * @param n
     *            The value to consume.
     */
    public void setToDouble(double n) {
        setBcdToZero();
        flags = 0;
        // Double.compare() handles +0.0 vs -0.0
        if (Double.compare(n, 0.0) < 0) {
            flags |= NEGATIVE_FLAG;
            n = -n;
        }
        if (Double.isNaN(n)) {
            flags |= NAN_FLAG;
        } else if (Double.isInfinite(n)) {
            flags |= INFINITY_FLAG;
        } else if (n != 0) {
            _setToDoubleFast(n);
            compact();
        }
    }

    private static final double[] DOUBLE_MULTIPLIERS = {
            1e0,
            1e1,
            1e2,
            1e3,
            1e4,
            1e5,
            1e6,
            1e7,
            1e8,
            1e9,
            1e10,
            1e11,
            1e12,
            1e13,
            1e14,
            1e15,
            1e16,
            1e17,
            1e18,
            1e19,
            1e20,
            1e21 };

    /**
     * Uses double multiplication and division to get the number into integer space before converting to
     * digits. Since double arithmetic is inexact, the resulting digits may not be accurate.
     */
    private void _setToDoubleFast(double n) {
        isApproximate = true;
        origDouble = n;
        origDelta = 0;

        // NOTE: Unlike ICU4C, doubles are always IEEE 754 doubles.
        long ieeeBits = Double.doubleToLongBits(n);
        int exponent = (int) ((ieeeBits & 0x7ff0000000000000L) >> 52) - 0x3ff;

        // Not all integers can be represented exactly for exponent > 52
        if (exponent <= 52 && (long) n == n) {
            _setToLong((long) n);
            return;
        }

        // 3.3219... is log2(10)
        int fracLength = (int) ((52 - exponent) / 3.32192809489);
        if (fracLength >= 0) {
            int i = fracLength;
            // 1e22 is the largest exact double.
            for (; i >= 22; i -= 22)
                n *= 1e22;
            n *= DOUBLE_MULTIPLIERS[i];
        } else {
            int i = fracLength;
            // 1e22 is the largest exact double.
            for (; i <= -22; i += 22)
                n /= 1e22;
            n /= DOUBLE_MULTIPLIERS[-i];
        }
        long result = Math.round(n);
        if (result != 0) {
            _setToLong(result);
            scale -= fracLength;
        }
    }

    /**
     * Uses Double.toString() to obtain an exact accurate representation of the double, overwriting it
     * into the BCD. This method can be called at any point after {@link #_setToDoubleFast} while
     * {@link #isApproximate} is still true.
     */
    private void convertToAccurateDouble() {
        double n = origDouble;
        assert n != 0;
        int delta = origDelta;
        setBcdToZero();

        // Call the slow oracle function (Double.toString in Java, sprintf in C++).
        String dstr = Double.toString(n);

        if (dstr.indexOf('E') != -1) {
            // Case 1: Exponential notation.
            assert dstr.indexOf('.') == 1;
            int expPos = dstr.indexOf('E');
            _setToLong(Long.parseLong(dstr.charAt(0) + dstr.substring(2, expPos)));
            scale += Integer.parseInt(dstr.substring(expPos + 1)) - (expPos - 1) + 1;
        } else if (dstr.charAt(0) == '0') {
            // Case 2: Fraction-only number.
            assert dstr.indexOf('.') == 1;
            _setToLong(Long.parseLong(dstr.substring(2)));
            scale += 2 - dstr.length();
        } else if (dstr.charAt(dstr.length() - 1) == '0') {
            // Case 3: Integer-only number.
            // Note: this path should not normally happen, because integer-only numbers are captured
            // before the approximate double logic is performed.
            assert dstr.indexOf('.') == dstr.length() - 2;
            assert dstr.length() - 2 <= 18;
            _setToLong(Long.parseLong(dstr.substring(0, dstr.length() - 2)));
            // no need to adjust scale
        } else {
            // Case 4: Number with both a fraction and an integer.
            int decimalPos = dstr.indexOf('.');
            _setToLong(Long.parseLong(dstr.substring(0, decimalPos) + dstr.substring(decimalPos + 1)));
            scale += decimalPos - dstr.length() + 1;
        }

        scale += delta;
        compact();
        explicitExactDouble = true;
    }

    /**
     * Whether this {@link DecimalQuantity_DualStorageBCD} has been explicitly converted to an exact
     * double. true if backed by a double that was explicitly converted via convertToAccurateDouble;
     * false otherwise. Used for testing.
     *
     * @internal
     * @deprecated This API is ICU internal only.
     */
    @Deprecated
    public boolean explicitExactDouble = false;

    /**
     * Sets the internal BCD state to represent the value in the given BigDecimal.
     *
     * @param n
     *            The value to consume.
     */
    @Override
    public void setToBigDecimal(BigDecimal n) {
        setBcdToZero();
        flags = 0;
        if (n.signum() == -1) {
            flags |= NEGATIVE_FLAG;
            n = n.negate();
        }
        if (n.signum() != 0) {
            _setToBigDecimal(n);
            compact();
        }
    }

    private void _setToBigDecimal(BigDecimal n) {
        int fracLength = n.scale();
        n = n.scaleByPowerOfTen(fracLength);
        BigInteger bi = n.toBigInteger();
        _setToBigInteger(bi);
        scale -= fracLength;
    }

    /**
     * Returns a long approximating the internal BCD. A long can only represent the integral part of the
     * number.
     *
     * @param truncateIfOverflow if false and the number does NOT fit, fails with an assertion error.
     * @return A 64-bit integer representation of the internal BCD.
     */
    public long toLong(boolean truncateIfOverflow) {
        // NOTE: Call sites should be guarded by fitsInLong(), like this:
        // if (dq.fitsInLong()) { /* use dq.toLong() */ } else { /* use some fallback */ }
        // Fallback behavior upon truncateIfOverflow is to truncate at 17 digits.
        assert(truncateIfOverflow || fitsInLong());
        long result = 0L;
        int upperMagnitude = scale + precision - 1;
        if (truncateIfOverflow) {
            upperMagnitude = Math.min(upperMagnitude, 17);
        }
        for (int magnitude = upperMagnitude; magnitude >= 0; magnitude--) {
            result = result * 10 + getDigitPos(magnitude - scale);
        }
        if (isNegative()) {
            result = -result;
        }
        return result;
    }

    /**
     * This returns a long representing the fraction digits of the number, as required by PluralRules.
     * For example, if we represent the number "1.20" (including optional and required digits), then this
     * function returns "20" if includeTrailingZeros is true or "2" if false.
     */
    public long toFractionLong(boolean includeTrailingZeros) {
        long result = 0L;
        int magnitude = -1;
        int lowerMagnitude = scale;
        if (includeTrailingZeros) {
            lowerMagnitude = Math.min(lowerMagnitude, rReqPos);
        }
        // NOTE: Java has only signed longs, so we check result <= 1e17 instead of 1e18
        for (; magnitude >= lowerMagnitude && result <= 1e17; magnitude--) {
            result = result * 10 + getDigitPos(magnitude - scale);
        }
        // Remove trailing zeros; this can happen during integer overflow cases.
        if (!includeTrailingZeros) {
            while (result > 0 && (result % 10) == 0) {
                result /= 10;
            }
        }
        return result;
    }

    static final byte[] INT64_BCD = { 9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 8 };

    /**
     * Returns whether or not a Long can fully represent the value stored in this DecimalQuantity.
     */
    public boolean fitsInLong() {
        if (isZero()) {
            return true;
        }
        if (scale < 0) {
            return false;
        }
        int magnitude = getMagnitude();
        if (magnitude < 18) {
            return true;
        }
        if (magnitude > 18) {
            return false;
        }
        // Hard case: the magnitude is 10^18.
        // The largest int64 is: 9,223,372,036,854,775,807
        for (int p = 0; p < precision; p++) {
            byte digit = getDigit(18 - p);
            if (digit < INT64_BCD[p]) {
                return true;
            } else if (digit > INT64_BCD[p]) {
                return false;
            }
        }
        // Exactly equal to max long plus one.
        return isNegative();
    }

    /**
     * Returns a double approximating the internal BCD. The double may not retain all of the information
     * encoded in the BCD if the BCD represents a number out of range of a double.
     *
     * @return A double representation of the internal BCD.
     */
    @Override
    public double toDouble() {
        // If this assertion fails, you need to call roundToInfinity() or some other rounding method.
        // See the comment at the top of this file explaining the "isApproximate" field.
        assert !isApproximate;

        if (isNaN()) {
            return Double.NaN;
        } else if (isInfinite()) {
            return isNegative() ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY;
        }

        StringBuilder sb = new StringBuilder();
        toScientificString(sb);
        return Double.valueOf(sb.toString());
    }

    @Override
    public BigDecimal toBigDecimal() {
        if (isApproximate) {
            // Converting to a BigDecimal requires Double.toString().
            convertToAccurateDouble();
        }
        return bcdToBigDecimal();
    }

    private static int safeSubtract(int a, int b) {
        int diff = a - b;
        if (b < 0 && diff < a)
            return Integer.MAX_VALUE;
        if (b > 0 && diff > a)
            return Integer.MIN_VALUE;
        return diff;
    }

    private static final int SECTION_LOWER_EDGE = -1;
    private static final int SECTION_UPPER_EDGE = -2;

    /** Removes all fraction digits. */
    public void truncate() {
        if (scale < 0) {
            shiftRight(-scale);
            scale = 0;
            compact();
        }
    }

    @Override
    public void roundToNickel(int magnitude, MathContext mathContext) {
        roundToMagnitude(magnitude, mathContext, true);
    }

    @Override
    public void roundToMagnitude(int magnitude, MathContext mathContext) {
        roundToMagnitude(magnitude, mathContext, false);
    }

    private void roundToMagnitude(int magnitude, MathContext mathContext, boolean nickel) {
        // The position in the BCD at which rounding will be performed; digits to the right of position
        // will be rounded away.
        int position = safeSubtract(magnitude, scale);

        // Enforce the number of digits required by the MathContext.
        int _mcPrecision = mathContext.getPrecision();
        if (_mcPrecision > 0 && precision - _mcPrecision > position) {
            position = precision - _mcPrecision;
        }

        // "trailing" = least significant digit to the left of rounding
        byte trailingDigit = getDigitPos(position);

        if (position <= 0 && !isApproximate && (!nickel || trailingDigit == 0 || trailingDigit == 5)) {
            // All digits are to the left of the rounding magnitude.
        } else if (precision == 0) {
            // No rounding for zero.
        } else {
            // Perform rounding logic.
            // "leading" = most significant digit to the right of rounding
            byte leadingDigit = getDigitPos(safeSubtract(position, 1));

            // Compute which section of the number we are in.
            // EDGE means we are at the bottom or top edge, like 1.000 or 1.999 (used by doubles)
            // LOWER means we are between the bottom edge and the midpoint, like 1.391
            // MIDPOINT means we are exactly in the middle, like 1.500
            // UPPER means we are between the midpoint and the top edge, like 1.916
            int section;
            if (!isApproximate) {
                if (nickel && trailingDigit != 2 && trailingDigit != 7) {
                    // Nickel rounding, and not at .02x or .07x
                    if (trailingDigit < 2) {
                        // .00, .01 => down to .00
                        section = RoundingUtils.SECTION_LOWER;
                    } else if (trailingDigit < 5) {
                        // .03, .04 => up to .05
                        section = RoundingUtils.SECTION_UPPER;
                    } else if (trailingDigit < 7) {
                        // .05, .06 => down to .05
                        section = RoundingUtils.SECTION_LOWER;
                    } else {
                        // .08, .09 => up to .10
                        section = RoundingUtils.SECTION_UPPER;
                    }
                } else if (leadingDigit < 5) {
                    // Includes nickel rounding .020-.024 and .070-.074
                    section = RoundingUtils.SECTION_LOWER;
                } else if (leadingDigit > 5) {
                    // Includes nickel rounding .026-.029 and .076-.079
                    section = RoundingUtils.SECTION_UPPER;
                } else {
                    // Includes nickel rounding .025 and .075
                    section = RoundingUtils.SECTION_MIDPOINT;
                    for (int p = safeSubtract(position, 2); p >= 0; p--) {
                        if (getDigitPos(p) != 0) {
                            section = RoundingUtils.SECTION_UPPER;
                            break;
                        }
                    }
                }
            } else {
                int p = safeSubtract(position, 2);
                int minP = Math.max(0, precision - 14);
                if (leadingDigit == 0 && (!nickel || trailingDigit == 0 || trailingDigit == 5)) {
                    section = SECTION_LOWER_EDGE;
                    for (; p >= minP; p--) {
                        if (getDigitPos(p) != 0) {
                            section = RoundingUtils.SECTION_LOWER;
                            break;
                        }
                    }
                } else if (leadingDigit == 4 && (!nickel || trailingDigit == 2 || trailingDigit == 7)) {
                    section = RoundingUtils.SECTION_MIDPOINT;
                    for (; p >= minP; p--) {
                        if (getDigitPos(p) != 9) {
                            section = RoundingUtils.SECTION_LOWER;
                            break;
                        }
                    }
                } else if (leadingDigit == 5 && (!nickel || trailingDigit == 2 || trailingDigit == 7)) {
                    section = RoundingUtils.SECTION_MIDPOINT;
                    for (; p >= minP; p--) {
                        if (getDigitPos(p) != 0) {
                            section = RoundingUtils.SECTION_UPPER;
                            break;
                        }
                    }
                } else if (leadingDigit == 9 && (!nickel || trailingDigit == 4 || trailingDigit == 9)) {
                    section = SECTION_UPPER_EDGE;
                    for (; p >= minP; p--) {
                        if (getDigitPos(p) != 9) {
                            section = RoundingUtils.SECTION_UPPER;
                            break;
                        }
                    }
                } else if (nickel && trailingDigit != 2 && trailingDigit != 7) {
                    // Nickel rounding, and not at .02x or .07x
                    if (trailingDigit < 2) {
                        // .00, .01 => down to .00
                        section = RoundingUtils.SECTION_LOWER;
                    } else if (trailingDigit < 5) {
                        // .03, .04 => up to .05
                        section = RoundingUtils.SECTION_UPPER;
                    } else if (trailingDigit < 7) {
                        // .05, .06 => down to .05
                        section = RoundingUtils.SECTION_LOWER;
                    } else {
                        // .08, .09 => up to .10
                        section = RoundingUtils.SECTION_UPPER;
                    }
                } else if (leadingDigit < 5) {
                    // Includes nickel rounding .020-.024 and .070-.074
                    section = RoundingUtils.SECTION_LOWER;
                } else {
                    // Includes nickel rounding .026-.029 and .076-.079
                    section = RoundingUtils.SECTION_UPPER;
                }

                boolean roundsAtMidpoint = RoundingUtils
                        .roundsAtMidpoint(mathContext.getRoundingMode().ordinal());
                if (safeSubtract(position, 1) < precision - 14
                        || (roundsAtMidpoint && section == RoundingUtils.SECTION_MIDPOINT)
                        || (!roundsAtMidpoint && section < 0 /* i.e. at upper or lower edge */)) {
                    // Oops! This means that we have to get the exact representation of the double,
                    // because the zone of uncertainty is along the rounding boundary.
                    convertToAccurateDouble();
                    roundToMagnitude(magnitude, mathContext, nickel); // start over
                    return;
                }

                // Turn off the approximate double flag, since the value is now confirmed to be exact.
                isApproximate = false;
                origDouble = 0.0;
                origDelta = 0;

                if (position <= 0 && (!nickel || trailingDigit == 0 || trailingDigit == 5)) {
                    // All digits are to the left of the rounding magnitude.
                    return;
                }

                // Good to continue rounding.
                if (section == SECTION_LOWER_EDGE)
                    section = RoundingUtils.SECTION_LOWER;
                if (section == SECTION_UPPER_EDGE)
                    section = RoundingUtils.SECTION_UPPER;
            }

            // Nickel rounding "half even" goes to the nearest whole (away from the 5).
            boolean isEven = nickel
                    ? (trailingDigit < 2 || trailingDigit > 7
                            || (trailingDigit == 2 && section != RoundingUtils.SECTION_UPPER)
                            || (trailingDigit == 7 && section == RoundingUtils.SECTION_UPPER))
                    : (trailingDigit % 2) == 0;

            boolean roundDown = RoundingUtils.getRoundingDirection(isEven,
                    isNegative(),
                    section,
                    mathContext.getRoundingMode().ordinal(),
                    this);

            // Perform truncation
            if (position >= precision) {
                setBcdToZero();
                scale = magnitude;
            } else {
                shiftRight(position);
            }

            if (nickel) {
                if (trailingDigit < 5 && roundDown) {
                    setDigitPos(0, (byte) 0);
                    compact();
                    return;
                } else if (trailingDigit >= 5 && !roundDown) {
                    setDigitPos(0, (byte) 9);
                    trailingDigit = 9;
                    // do not return: use the bubbling logic below
                } else {
                    setDigitPos(0, (byte) 5);
                    // compact not necessary: digit at position 0 is nonzero
                    return;
                }
            }

            // Bubble the result to the higher digits
            if (!roundDown) {
                if (trailingDigit == 9) {
                    int bubblePos = 0;
                    // Note: in the long implementation, the most digits BCD can have at this point is
                    // 15, so bubblePos <= 15 and getDigitPos(bubblePos) is safe.
                    for (; getDigitPos(bubblePos) == 9; bubblePos++) {
                    }
                    shiftRight(bubblePos); // shift off the trailing 9s
                }
                byte digit0 = getDigitPos(0);
                assert digit0 != 9;
                setDigitPos(0, (byte) (digit0 + 1));
                precision += 1; // in case an extra digit got added
            }

            compact();
        }
    }

    @Override
    public void roundToInfinity() {
        if (isApproximate) {
            convertToAccurateDouble();
        }
    }

    /**
     * Appends a digit, optionally with one or more leading zeros, to the end of the value represented by
     * this DecimalQuantity.
     *
     * <p>
     * The primary use of this method is to construct numbers during a parsing loop. It allows parsing to
     * take advantage of the digit list infrastructure primarily designed for formatting.
     *
     * @param value
     *            The digit to append.
     * @param leadingZeros
     *            The number of zeros to append before the digit. For example, if the value in this
     *            instance starts as 12.3, and you append a 4 with 1 leading zero, the value becomes
     *            12.304.
     * @param appendAsInteger
     *            If true, increase the magnitude of existing digits to make room for the new digit. If
     *            false, append to the end like a fraction digit. If true, there must not be any fraction
     *            digits already in the number.
     * @internal
     * @deprecated This API is ICU internal only.
     */
    @Deprecated
    public void appendDigit(byte value, int leadingZeros, boolean appendAsInteger) {
        assert leadingZeros >= 0;

        // Zero requires special handling to maintain the invariant that the least-significant digit
        // in the BCD is nonzero.
        if (value == 0) {
            if (appendAsInteger && precision != 0) {
                scale += leadingZeros + 1;
            }
            return;
        }

        // Deal with trailing zeros
        if (scale > 0) {
            leadingZeros += scale;
            if (appendAsInteger) {
                scale = 0;
            }
        }

        // Append digit
        shiftLeft(leadingZeros + 1);
        setDigitPos(0, value);

        // Fix scale if in integer mode
        if (appendAsInteger) {
            scale += leadingZeros + 1;
        }
    }

    @Override
    public String toPlainString() {
        // NOTE: This logic is duplicated between here and DecimalQuantity_SimpleStorage.
        StringBuilder sb = new StringBuilder();
        if (isNegative()) {
            sb.append('-');
        }
        if (precision == 0 || getMagnitude() < 0) {
            sb.append('0');
        }
        for (int m = getUpperDisplayMagnitude(); m >= getLowerDisplayMagnitude(); m--) {
            sb.append((char) ('0' + getDigit(m)));
            if (m == 0)
                sb.append('.');
        }
        return sb.toString();
    }

    public String toScientificString() {
        StringBuilder sb = new StringBuilder();
        toScientificString(sb);
        return sb.toString();
    }

    public void toScientificString(StringBuilder result) {
        assert(!isApproximate);
        if (isNegative()) {
            result.append('-');
        }
        if (precision == 0) {
            result.append("0E+0");
            return;
        }
        // NOTE: It is not safe to add to lOptPos (aka maxInt) or subtract from
        // rOptPos (aka -maxFrac) due to overflow.
        int upperPos = precision - 1;
        int lowerPos = 0;
        int p = upperPos;
        result.append((char) ('0' + getDigitPos(p)));
        if ((--p) >= lowerPos) {
            result.append('.');
            for (; p >= lowerPos; p--) {
                result.append((char) ('0' + getDigitPos(p)));
            }
        }
        result.append('E');
        int _scale = upperPos + scale;
        if (_scale == Integer.MIN_VALUE) {
            result.append("-2147483648");
            return;
        } else if (_scale < 0) {
            _scale *= -1;
            result.append('-');
        } else {
            result.append('+');
        }
        if (_scale == 0) {
            result.append('0');
        }
        int insertIndex = result.length();
        while (_scale > 0) {
            int quot = _scale / 10;
            int rem = _scale % 10;
            result.insert(insertIndex, (char) ('0' + rem));
            _scale = quot;
        }
    }

    @Override
    public boolean equals(Object other) {
        if (this == other) {
            return true;
        }
        if (other == null) {
            return false;
        }
        if (!(other instanceof DecimalQuantity_AbstractBCD)) {
            return false;
        }
        DecimalQuantity_AbstractBCD _other = (DecimalQuantity_AbstractBCD) other;

        boolean basicEquals =
                scale == _other.scale
                && precision == _other.precision
                && flags == _other.flags
                && lReqPos == _other.lReqPos
                && rReqPos == _other.rReqPos
                && isApproximate == _other.isApproximate;
        if (!basicEquals) {
            return false;
        }

        if (precision == 0) {
            return true;
        } else if (isApproximate) {
            return origDouble == _other.origDouble && origDelta == _other.origDelta;
        } else {
            for (int m = getUpperDisplayMagnitude(); m >= getLowerDisplayMagnitude(); m--) {
                if (getDigit(m) != _other.getDigit(m)) {
                    return false;
                }
            }
            return true;
        }
    }

    /**
     * Returns a single digit from the BCD list. No internal state is changed by calling this method.
     *
     * @param position
     *            The position of the digit to pop, counted in BCD units from the least significant
     *            digit. If outside the range supported by the implementation, zero is returned.
     * @return The digit at the specified location.
     */
    protected abstract byte getDigitPos(int position);

    /**
     * Sets the digit in the BCD list. This method only sets the digit; it is the caller's responsibility
     * to call {@link #compact} after setting the digit.
     *
     * @param position
     *            The position of the digit to pop, counted in BCD units from the least significant
     *            digit. If outside the range supported by the implementation, an AssertionError is
     *            thrown.
     * @param value
     *            The digit to set at the specified location.
     */
    protected abstract void setDigitPos(int position, byte value);

    /**
     * Adds zeros to the end of the BCD list. This will result in an invalid BCD representation; it is
     * the caller's responsibility to do further manipulation and then call {@link #compact}.
     *
     * @param numDigits
     *            The number of zeros to add.
     */
    protected abstract void shiftLeft(int numDigits);

    /**
     * Removes digits from the end of the BCD list. This may result in an invalid BCD representation; it
     * is the caller's responsibility to follow-up with a call to {@link #compact}.
     *
     * @param numDigits
     *            The number of digits to remove.
     */
    protected abstract void shiftRight(int numDigits);

    /**
     * Directly removes digits from the front of the BCD list.
     * Updates precision.
     *
     * CAUTION: it is the caller's responsibility to call {@link #compact} after this method.
     */
    protected abstract void popFromLeft(int numDigits);

    /**
     * Sets the internal representation to zero. Clears any values stored in scale, precision, hasDouble,
     * origDouble, origDelta, and BCD data.
     */
    protected abstract void setBcdToZero();

    /**
     * Sets the internal BCD state to represent the value in the given int. The int is guaranteed to be
     * either positive. The internal state is guaranteed to be empty when this method is called.
     *
     * @param n
     *            The value to consume.
     */
    protected abstract void readIntToBcd(int input);

    /**
     * Sets the internal BCD state to represent the value in the given long. The long is guaranteed to be
     * either positive. The internal state is guaranteed to be empty when this method is called.
     *
     * @param n
     *            The value to consume.
     */
    protected abstract void readLongToBcd(long input);

    /**
     * Sets the internal BCD state to represent the value in the given BigInteger. The BigInteger is
     * guaranteed to be positive, and it is guaranteed to be larger than Long.MAX_VALUE. The internal
     * state is guaranteed to be empty when this method is called.
     *
     * @param n
     *            The value to consume.
     */
    protected abstract void readBigIntegerToBcd(BigInteger input);

    /**
     * Returns a BigDecimal encoding the internal BCD value.
     *
     * @return A BigDecimal representation of the internal BCD.
     */
    protected abstract BigDecimal bcdToBigDecimal();

    protected abstract void copyBcdFrom(DecimalQuantity _other);

    /**
     * Removes trailing zeros from the BCD (adjusting the scale as required) and then computes the
     * precision. The precision is the number of digits in the number up through the greatest nonzero
     * digit.
     *
     * <p>
     * This method must always be called when bcd changes in order for assumptions to be correct in
     * methods like {@link #fractionCount()}.
     */
    protected abstract void compact();
}
