// © 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.MathContext; import java.math.RoundingMode; import java.text.FieldPosition; import com.ibm.icu.impl.StandardPlural; import com.ibm.icu.text.PluralRules; import com.ibm.icu.text.PluralRules.Operand; import com.ibm.icu.text.UFieldPosition; /** * This is an older implementation of DecimalQuantity. A newer, faster implementation is * DecimalQuantity2. I kept this implementation around because it was useful for testing purposes * (being able to compare the output of one implementation with the other). * *

This class is NOT IMMUTABLE and NOT THREAD SAFE and is intended to be used by a single thread * to format a number through a formatter, which is thread-safe. */ public class DecimalQuantity_SimpleStorage implements DecimalQuantity { // Four positions: left optional '(', left required '[', right required ']', right optional ')'. // These four positions determine which digits are displayed in the output string. They do NOT // affect rounding. These positions are internal-only and can be specified only by the public // endpoints like setFractionLength, setIntegerLength, and setSignificantDigits, among others. // // * Digits between lReqPos and rReqPos are in the "required zone" and are always displayed. // * Digits between lOptPos and rOptPos but outside the required zone are in the "optional zone" // and are displayed unless they are trailing off the left or right edge of the number and // have a numerical value of zero. In order to be "trailing", the digits need to be beyond // the decimal point in their respective directions. // * Digits outside of the "optional zone" are never displayed. // // See the table below for illustrative examples. // // +---------+---------+---------+---------+------------+------------------------+--------------+ // | lOptPos | lReqPos | rReqPos | rOptPos | number | positions | en-US string | // +---------+---------+---------+---------+------------+------------------------+--------------+ // | 5 | 2 | -1 | -5 | 1234.567 | ( 12[34.5]67 ) | 1,234.567 | // | 3 | 2 | -1 | -5 | 1234.567 | 1(2[34.5]67 ) | 234.567 | // | 3 | 2 | -1 | -2 | 1234.567 | 1(2[34.5]6)7 | 234.56 | // | 6 | 4 | 2 | -5 | 123456789. | 123(45[67]89. ) | 456,789. | // | 6 | 4 | 2 | 1 | 123456789. | 123(45[67]8)9. | 456,780. | // | -1 | -1 | -3 | -4 | 0.123456 | 0.1([23]4)56 | .0234 | // | 6 | 4 | -2 | -2 | 12.3 | ( [ 12.3 ]) | 0012.30 | // +---------+---------+---------+---------+------------+------------------------+--------------+ // private int lOptPos = Integer.MAX_VALUE; private int lReqPos = 0; private int rReqPos = 0; private int rOptPos = Integer.MIN_VALUE; // Internally, attempt to use a long to store the number. A long can hold numbers between 18 and // 19 digits, covering the vast majority of use cases. We store three values: the long itself, // the "scale" of the long (the power of 10 represented by the rightmost digit in the long), and // the "precision" (the number of digits in the long). "primary" and "primaryScale" are the only // two variables that are required for representing the number in memory. "primaryPrecision" is // saved only for the sake of performance enhancements when performing certain operations. It can // always be re-computed from "primary" and "primaryScale". private long primary; private int primaryScale; private int primaryPrecision; // If the decimal can't fit into the long, fall back to a BigDecimal. private BigDecimal fallback; // Other properties private int flags; private static final int NEGATIVE_FLAG = 1; private static final int INFINITY_FLAG = 2; private static final int NAN_FLAG = 4; private static final long[] POWERS_OF_TEN = { 1L, 10L, 100L, 1000L, 10000L, 100000L, 1000000L, 10000000L, 100000000L, 1000000000L, 10000000000L, 100000000000L, 1000000000000L, 10000000000000L, 100000000000000L, 1000000000000000L, 10000000000000000L, 100000000000000000L, 1000000000000000000L }; @Override public int maxRepresentableDigits() { return Integer.MAX_VALUE; } public DecimalQuantity_SimpleStorage(long input) { if (input < 0) { setNegative(true); input *= -1; } primary = input; primaryScale = 0; primaryPrecision = computePrecision(primary); fallback = null; } /** * Creates a DecimalQuantity from the given double value. Internally attempts several strategies * for converting the double to an exact representation, falling back on a BigDecimal if it fails * to do so. * * @param input The double to represent by this DecimalQuantity. */ public DecimalQuantity_SimpleStorage(double input) { if (input < 0) { setNegative(true); input *= -1; } // First try reading from IEEE bits. This is trivial only for doubles in [2^52, 2^64). If it // fails, we wasted only a few CPU cycles. long ieeeBits = Double.doubleToLongBits(input); int exponent = (int) ((ieeeBits & 0x7ff0000000000000L) >> 52) - 0x3ff; if (exponent >= 52 && exponent <= 63) { // We can convert this double directly to a long. long mantissa = (ieeeBits & 0x000fffffffffffffL) + 0x0010000000000000L; primary = (mantissa << (exponent - 52)); primaryScale = 0; primaryPrecision = computePrecision(primary); return; } // Now try parsing the string produced by Double.toString(). String temp = Double.toString(input); try { if (temp.length() == 3 && temp.equals("0.0")) { // Case 1: Zero. primary = 0L; primaryScale = 0; primaryPrecision = 0; } else if (temp.indexOf('E') != -1) { // Case 2: Exponential notation. assert temp.indexOf('.') == 1; int expPos = temp.indexOf('E'); primary = Long.parseLong(temp.charAt(0) + temp.substring(2, expPos)); primaryScale = Integer.parseInt(temp.substring(expPos + 1)) - (expPos - 1) + 1; primaryPrecision = expPos - 1; } else if (temp.charAt(0) == '0') { // Case 3: Fraction-only number. assert temp.indexOf('.') == 1; primary = Long.parseLong(temp.substring(2)); // ignores leading zeros primaryScale = 2 - temp.length(); primaryPrecision = computePrecision(primary); } else if (temp.charAt(temp.length() - 1) == '0') { // Case 4: Integer-only number. assert temp.indexOf('.') == temp.length() - 2; int rightmostNonzeroDigitIndex = temp.length() - 3; while (temp.charAt(rightmostNonzeroDigitIndex) == '0') { rightmostNonzeroDigitIndex -= 1; } primary = Long.parseLong(temp.substring(0, rightmostNonzeroDigitIndex + 1)); primaryScale = temp.length() - rightmostNonzeroDigitIndex - 3; primaryPrecision = rightmostNonzeroDigitIndex + 1; } else if (temp.equals("Infinity")) { // Case 5: Infinity. primary = 0; setInfinity(true); } else if (temp.equals("NaN")) { // Case 6: NaN. primary = 0; setNaN(true); } else { // Case 7: Number with both a fraction and an integer. int decimalPos = temp.indexOf('.'); primary = Long.parseLong(temp.substring(0, decimalPos) + temp.substring(decimalPos + 1)); primaryScale = decimalPos - temp.length() + 1; primaryPrecision = temp.length() - 1; } } catch (NumberFormatException e) { // The digits of the double can't fit into the long. primary = -1; fallback = new BigDecimal(temp); } } static final double LOG_2_OF_TEN = 3.32192809489; public DecimalQuantity_SimpleStorage(double input, boolean fast) { if (input < 0) { setNegative(true); input *= -1; } // Our strategy is to read all digits that are *guaranteed* to be valid without delving into // the IEEE rounding rules. This strategy might not end up with a perfect representation of // the fractional part of the double. long ieeeBits = Double.doubleToLongBits(input); int exponent = (int) ((ieeeBits & 0x7ff0000000000000L) >> 52) - 0x3ff; long mantissa = (ieeeBits & 0x000fffffffffffffL) + 0x0010000000000000L; if (exponent > 63) { throw new IllegalArgumentException(); // FIXME } else if (exponent >= 52) { primary = (mantissa << (exponent - 52)); primaryScale = 0; primaryPrecision = computePrecision(primary); return; } else if (exponent >= 0) { int shift = 52 - exponent; primary = (mantissa >> shift); // integer part int fractionCount = (int) (shift / LOG_2_OF_TEN); long fraction = (mantissa - (primary << shift)) + 1L; // TODO: Explain the +1L primary *= POWERS_OF_TEN[fractionCount]; for (int i = 0; i < fractionCount; i++) { long times10 = (fraction * 10L); long digit = times10 >> shift; assert digit >= 0 && digit < 10; primary += digit * POWERS_OF_TEN[fractionCount - i - 1]; fraction = times10 & ((1L << shift) - 1); } primaryScale = -fractionCount; primaryPrecision = computePrecision(primary); } else { throw new IllegalArgumentException(); // FIXME } } public DecimalQuantity_SimpleStorage(BigDecimal decimal) { setToBigDecimal(decimal); } public DecimalQuantity_SimpleStorage(DecimalQuantity_SimpleStorage other) { copyFrom(other); } @Override public void setToBigDecimal(BigDecimal decimal) { if (decimal.compareTo(BigDecimal.ZERO) < 0) { setNegative(true); decimal = decimal.negate(); } primary = -1; if (decimal.compareTo(BigDecimal.ZERO) == 0) { fallback = BigDecimal.ZERO; } else { fallback = decimal; } } @Override public DecimalQuantity_SimpleStorage createCopy() { return new DecimalQuantity_SimpleStorage(this); } /** * Make the internal state of this DecimalQuantity equal to another DecimalQuantity. * * @param other The template DecimalQuantity. All properties from this DecimalQuantity will be * copied into this DecimalQuantity. */ @Override public void copyFrom(DecimalQuantity other) { // TODO: Check before casting DecimalQuantity_SimpleStorage _other = (DecimalQuantity_SimpleStorage) other; lOptPos = _other.lOptPos; lReqPos = _other.lReqPos; rReqPos = _other.rReqPos; rOptPos = _other.rOptPos; primary = _other.primary; primaryScale = _other.primaryScale; primaryPrecision = _other.primaryPrecision; fallback = _other.fallback; flags = _other.flags; } @Override public long getPositionFingerprint() { long fingerprint = 0; fingerprint ^= lOptPos; fingerprint ^= (lReqPos << 16); fingerprint ^= ((long) rReqPos << 32); fingerprint ^= ((long) rOptPos << 48); return fingerprint; } /** * Utility method to compute the number of digits ("precision") in a long. * * @param input The long (which can't contain more than 19 digits). * @return The precision of the long. */ private static int computePrecision(long input) { int precision = 0; while (input > 0) { input /= 10; precision++; } return precision; } /** * Changes the internal representation from a long to a BigDecimal. Used only for operations that * don't support longs. */ private void convertToBigDecimal() { if (primary == -1) { return; } fallback = new BigDecimal(primary).scaleByPowerOfTen(primaryScale); primary = -1; } @Override public void setIntegerLength(int minInt, int maxInt) { // Graceful failures for bogus input minInt = Math.max(0, minInt); maxInt = Math.max(0, maxInt); // The minima must be less than or equal to the maxima if (maxInt < minInt) { minInt = maxInt; } // Save values into internal state // Negation is safe for minFrac/maxFrac because -Integer.MAX_VALUE > Integer.MIN_VALUE lOptPos = maxInt; lReqPos = minInt; } @Override public void setFractionLength(int minFrac, int maxFrac) { // Graceful failures for bogus input minFrac = Math.max(0, minFrac); maxFrac = Math.max(0, maxFrac); // The minima must be less than or equal to the maxima if (maxFrac < minFrac) { minFrac = maxFrac; } // Save values into internal state // Negation is safe for minFrac/maxFrac because -Integer.MAX_VALUE > Integer.MIN_VALUE rReqPos = -minFrac; rOptPos = -maxFrac; } @Override public void roundToIncrement(BigDecimal roundingInterval, MathContext mathContext) { BigDecimal d = (primary == -1) ? fallback : new BigDecimal(primary).scaleByPowerOfTen(primaryScale); if (isNegative()) d = d.negate(); d = d.divide(roundingInterval, 0, mathContext.getRoundingMode()).multiply(roundingInterval); if (isNegative()) d = d.negate(); fallback = d; primary = -1; } @Override public void roundToMagnitude(int roundingMagnitude, MathContext mathContext) { if (roundingMagnitude < -1000) { roundToInfinity(); return; } if (primary == -1) { if (isNegative()) fallback = fallback.negate(); fallback = fallback.setScale(-roundingMagnitude, mathContext.getRoundingMode()); if (isNegative()) fallback = fallback.negate(); // Enforce the math context. fallback = fallback.round(mathContext); } else { int relativeScale = primaryScale - roundingMagnitude; if (relativeScale < -18) { // No digits will remain after rounding the number. primary = 0L; primaryScale = roundingMagnitude; primaryPrecision = 0; } else if (relativeScale < 0) { // This is the harder case, when we need to perform the rounding logic. // First check if the rightmost digits are already zero, where we can skip rounding. if ((primary % POWERS_OF_TEN[0 - relativeScale]) == 0) { // No rounding is necessary. } else { // TODO: Make this more efficient. Temporarily, convert to a BigDecimal and back again. BigDecimal temp = new BigDecimal(primary).scaleByPowerOfTen(primaryScale); if (isNegative()) temp = temp.negate(); temp = temp.setScale(-roundingMagnitude, mathContext.getRoundingMode()); if (isNegative()) temp = temp.negate(); temp = temp.scaleByPowerOfTen(-roundingMagnitude); primary = temp.longValueExact(); // should never throw primaryScale = roundingMagnitude; primaryPrecision = computePrecision(primary); } } else { // No rounding is necessary. All digits are to the left of the rounding magnitude. } // Enforce the math context. primary = new BigDecimal(primary).round(mathContext).longValueExact(); primaryPrecision = computePrecision(primary); } } @Override public void roundToInfinity() { // noop } /** * Multiply the internal number by the specified multiplicand. This method forces the internal * representation into a BigDecimal. If you are multiplying by a power of 10, use {@link * #adjustMagnitude} instead. * * @param multiplicand The number to be passed to {@link BigDecimal#multiply}. */ @Override public void multiplyBy(BigDecimal multiplicand) { convertToBigDecimal(); fallback = fallback.multiply(multiplicand); if (fallback.compareTo(BigDecimal.ZERO) < 0) { setNegative(!isNegative()); fallback = fallback.negate(); } } /** * Divide the internal number by the specified quotient. This method forces the internal * representation into a BigDecimal. If you are dividing by a power of 10, use {@link * #adjustMagnitude} instead. * * @param divisor The number to be passed to {@link BigDecimal#divide}. * @param scale The scale of the final rounded number. More negative means more decimal places. * @param mathContext The math context to use if rounding is necessary. */ @SuppressWarnings("unused") private void divideBy(BigDecimal divisor, int scale, MathContext mathContext) { convertToBigDecimal(); // Negate the scale because BigDecimal's scale is defined as the inverse of our scale fallback = fallback.divide(divisor, -scale, mathContext.getRoundingMode()); if (fallback.compareTo(BigDecimal.ZERO) < 0) { setNegative(!isNegative()); fallback = fallback.negate(); } } @Override public boolean isZero() { if (primary == -1) { return fallback.compareTo(BigDecimal.ZERO) == 0; } else { return primary == 0; } } /** @return The power of ten of the highest digit represented by this DecimalQuantity */ @Override public int getMagnitude() throws ArithmeticException { int scale = (primary == -1) ? scaleBigDecimal(fallback) : primaryScale; int precision = (primary == -1) ? precisionBigDecimal(fallback) : primaryPrecision; if (precision == 0) { throw new ArithmeticException("Magnitude is not well-defined for zero"); } else { return scale + precision - 1; } } /** * Changes the magnitude of this DecimalQuantity. If the indices of the represented digits had been * previously specified, those indices are moved relative to the DecimalQuantity. * *

This method does NOT perform rounding. * * @param delta The number of powers of ten to shift (positive shifts to the left). */ @Override public void adjustMagnitude(int delta) { if (primary == -1) { fallback = fallback.scaleByPowerOfTen(delta); } else { primaryScale = addOrMaxValue(primaryScale, delta); } } private static int addOrMaxValue(int a, int b) { // Check for overflow, and return min/max value if overflow occurs. if (b < 0 && a + b > a) { return Integer.MIN_VALUE; } else if (b > 0 && a + b < a) { return Integer.MAX_VALUE; } return a + b; } /** @return If the number represented by this DecimalQuantity is less than zero */ @Override public boolean isNegative() { return (flags & NEGATIVE_FLAG) != 0; } private void setNegative(boolean isNegative) { flags = (flags & (~NEGATIVE_FLAG)) | (isNegative ? NEGATIVE_FLAG : 0); } @Override public boolean isInfinite() { return (flags & INFINITY_FLAG) != 0; } private void setInfinity(boolean isInfinity) { flags = (flags & (~INFINITY_FLAG)) | (isInfinity ? INFINITY_FLAG : 0); } @Override public boolean isNaN() { return (flags & NAN_FLAG) != 0; } private void setNaN(boolean isNaN) { flags = (flags & (~NAN_FLAG)) | (isNaN ? NAN_FLAG : 0); } /** * Returns a representation of this DecimalQuantity as a double, with possible loss of information. */ @Override public double toDouble() { double result; if (primary == -1) { result = fallback.doubleValue(); } else { // TODO: Make this more efficient result = primary; for (int i = 0; i < primaryScale; i++) { result *= 10.; } for (int i = 0; i > primaryScale; i--) { result /= 10.; } } return isNegative() ? -result : result; } @Override public BigDecimal toBigDecimal() { BigDecimal result; if (primary != -1) { result = new BigDecimal(primary).scaleByPowerOfTen(primaryScale); } else { result = fallback; } return isNegative() ? result.negate() : result; } @Override public StandardPlural getStandardPlural(PluralRules rules) { if (rules == null) { // Fail gracefully if the user didn't provide a PluralRules return StandardPlural.OTHER; } else { // TODO: Avoid converting to a double for the sake of PluralRules String ruleString = rules.select(toDouble()); return StandardPlural.orOtherFromString(ruleString); } } @Override public double getPluralOperand(Operand operand) { // TODO: This is a temporary hack. return new PluralRules.FixedDecimal(toDouble()).getPluralOperand(operand); } public boolean hasNextFraction() { if (rReqPos < 0) { // We are in the required zone. return true; } else if (rOptPos >= 0) { // We are in the forbidden zone. return false; } else { // We are in the optional zone. if (primary == -1) { return fallback.remainder(BigDecimal.ONE).compareTo(BigDecimal.ZERO) > 0; } else { if (primaryScale <= -19) { // The number is a fraction so small that it consists of only fraction digits. return primary > 0; } else if (primaryScale < 0) { // Check if we have a fraction part. long factor = POWERS_OF_TEN[0 - primaryScale]; return ((primary % factor) != 0); } else { // The lowest digit in the long has magnitude greater than -1. return false; } } } } public byte nextFraction() { byte returnValue; if (primary == -1) { BigDecimal temp = fallback.multiply(BigDecimal.TEN); returnValue = temp.setScale(0, RoundingMode.FLOOR).remainder(BigDecimal.TEN).byteValue(); fallback = fallback.setScale(0, RoundingMode.FLOOR).add(temp.remainder(BigDecimal.ONE)); } else { if (primaryScale <= -20) { // The number is a fraction so small that it has no first fraction digit. primaryScale += 1; returnValue = 0; } else if (primaryScale < 0) { // Extract the fraction digit out of the middle of the long. long factor = POWERS_OF_TEN[0 - primaryScale - 1]; long temp1 = primary / factor; long temp2 = primary % factor; returnValue = (byte) (temp1 % 10); // not necessarily nonzero primary = ((temp1 / 10) * factor) + temp2; primaryScale += 1; if (temp1 != 0) { primaryPrecision -= 1; } } else { // The lowest digit in the long has magnitude greater than -1. returnValue = 0; } } // Update digit brackets if (lOptPos < 0) { lOptPos += 1; } if (lReqPos < 0) { lReqPos += 1; } if (rReqPos < 0) { rReqPos += 1; } if (rOptPos < 0) { rOptPos += 1; } assert returnValue >= 0; return returnValue; } public boolean hasNextInteger() { if (lReqPos > 0) { // We are in the required zone. return true; } else if (lOptPos <= 0) { // We are in the forbidden zone. return false; } else { // We are in the optional zone. if (primary == -1) { return fallback.setScale(0, RoundingMode.FLOOR).compareTo(BigDecimal.ZERO) > 0; } else { if (primaryScale < -18) { // The number is a fraction so small that it has no integer part. return false; } else if (primaryScale < 0) { // Check if we have an integer part. long factor = POWERS_OF_TEN[0 - primaryScale]; return ((primary % factor) != primary); // equivalent: ((primary / 10) != 0) } else { // The lowest digit in the long has magnitude of at least 0. return primary != 0; } } } } private int integerCount() { int digitsRemaining; if (primary == -1) { digitsRemaining = precisionBigDecimal(fallback) + scaleBigDecimal(fallback); } else { digitsRemaining = primaryPrecision + primaryScale; } return Math.min(Math.max(digitsRemaining, lReqPos), lOptPos); } private int fractionCount() { // TODO: This is temporary. DecimalQuantity_SimpleStorage copy = new DecimalQuantity_SimpleStorage(this); int fractionCount = 0; while (copy.hasNextFraction()) { copy.nextFraction(); fractionCount++; } return fractionCount; } @Override public int getUpperDisplayMagnitude() { return integerCount() - 1; } @Override public int getLowerDisplayMagnitude() { return -fractionCount(); } // @Override // public byte getIntegerDigit(int index) { // return getDigitPos(index); // } // // @Override // public byte getFractionDigit(int index) { // return getDigitPos(-index - 1); // } @Override public byte getDigit(int magnitude) { // TODO: This is temporary. DecimalQuantity_SimpleStorage copy = new DecimalQuantity_SimpleStorage(this); if (magnitude < 0) { for (int p = -1; p > magnitude; p--) { copy.nextFraction(); } return copy.nextFraction(); } else { for (int p = 0; p < magnitude; p++) { copy.nextInteger(); } return copy.nextInteger(); } } public byte nextInteger() { byte returnValue; if (primary == -1) { returnValue = fallback.setScale(0, RoundingMode.FLOOR).remainder(BigDecimal.TEN).byteValue(); BigDecimal temp = fallback.divide(BigDecimal.TEN).setScale(0, RoundingMode.FLOOR); fallback = fallback.remainder(BigDecimal.ONE).add(temp); } else { if (primaryScale < -18) { // The number is a fraction so small that it has no integer part. returnValue = 0; } else if (primaryScale < 0) { // Extract the integer digit out of the middle of the long. In many ways, this is the heart // of the digit iterator algorithm. long factor = POWERS_OF_TEN[0 - primaryScale]; if ((primary % factor) != primary) { // equivalent: ((primary / 10) != 0) returnValue = (byte) ((primary / factor) % 10); long temp = (primary / 10); primary = temp - (temp % factor) + (primary % factor); primaryPrecision -= 1; } else { returnValue = 0; } } else if (primaryScale == 0) { // Fast-path for primaryScale == 0 (otherwise equivalent to previous step). if (primary != 0) { returnValue = (byte) (primary % 10); primary /= 10; primaryPrecision -= 1; } else { returnValue = 0; } } else { // The lowest digit in the long has magnitude greater than 0. primaryScale -= 1; returnValue = 0; } } // Update digit brackets if (lOptPos > 0) { lOptPos -= 1; } if (lReqPos > 0) { lReqPos -= 1; } if (rReqPos > 0) { rReqPos -= 1; } if (rOptPos > 0) { rOptPos -= 1; } assert returnValue >= 0; return returnValue; } /** * Helper method to compute the precision of a BigDecimal by our definition of precision, which is * that the number zero gets precision zero. * * @param decimal The BigDecimal whose precision to compute. * @return The precision by our definition. */ private static int precisionBigDecimal(BigDecimal decimal) { if (decimal.compareTo(BigDecimal.ZERO) == 0) { return 0; } else { return decimal.precision(); } } /** * Helper method to compute the scale of a BigDecimal by our definition of scale, which is that * deeper fractions result in negative scales as opposed to positive scales. * * @param decimal The BigDecimal whose scale to compute. * @return The scale by our definition. */ private static int scaleBigDecimal(BigDecimal decimal) { return -decimal.scale(); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(" 1000 ? "max" : lOptPos); sb.append(":"); sb.append(lReqPos); sb.append(":"); sb.append(rReqPos); sb.append(":"); sb.append(rOptPos < -1000 ? "min" : rOptPos); sb.append(" "); sb.append(fallback.toString()); } else { String digits = Long.toString(primary); int iDec = digits.length() + primaryScale; int iLP = iDec - toRange(lOptPos, -1000, 1000); int iLB = iDec - toRange(lReqPos, -1000, 1000); int iRB = iDec - toRange(rReqPos, -1000, 1000); int iRP = iDec - toRange(rOptPos, -1000, 1000); iDec = Math.max(Math.min(iDec, digits.length() + 1), -1); iLP = Math.max(Math.min(iLP, digits.length() + 1), -1); iLB = Math.max(Math.min(iLB, digits.length() + 1), -1); iRB = Math.max(Math.min(iRB, digits.length() + 1), -1); iRP = Math.max(Math.min(iRP, digits.length() + 1), -1); for (int i = -1; i <= digits.length() + 1; i++) { if (i == iLP) sb.append('('); if (i == iLB) sb.append('['); if (i == iDec) sb.append('.'); if (i == iRB) sb.append(']'); if (i == iRP) sb.append(')'); if (i >= 0 && i < digits.length()) sb.append(digits.charAt(i)); else sb.append('\u00A0'); } } sb.append(">"); return sb.toString(); } @Override public String toPlainString() { // NOTE: This logic is duplicated between here and DecimalQuantity_AbstractBCD. StringBuilder sb = new StringBuilder(); if (isNegative()) { sb.append('-'); } for (int m = getUpperDisplayMagnitude(); m >= getLowerDisplayMagnitude(); m--) { sb.append(getDigit(m)); if (m == 0) sb.append('.'); } return sb.toString(); } private static int toRange(int i, int lo, int hi) { if (i < lo) { return lo; } else if (i > hi) { return hi; } else { return i; } } @Override public void populateUFieldPosition(FieldPosition fp) { if (fp instanceof UFieldPosition) { ((UFieldPosition) fp) .setFractionDigits((int) getPluralOperand(Operand.v), (long) getPluralOperand(Operand.f)); } } }