com.ibm.icu.impl.number.DecimalQuantity_AbstractBCD Maven / Gradle / Ivy
Show all versions of icu4j Show documentation
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
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.impl.number.Modifier.Signum;
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.
*
*
* 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.
*
*
* 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;
/**
* The value of the (suppressed) exponent after the number has been put into
* a notation with exponents (ex: compact, scientific).
*/
protected int exponent = 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;
exponent = other.exponent;
}
public DecimalQuantity_AbstractBCD clear() {
lReqPos = 0;
rReqPos = 0;
flags = 0;
setBcdToZero(); // sets scale, precision, hasDouble, origDouble, origDelta, exponent, 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 (isZeroish()) {
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 int getExponent() {
return exponent;
}
@Override
public void adjustExponent(int delta) {
exponent = exponent + delta;
}
@Override
public void resetExponent() {
adjustMagnitude(exponent);
exponent = 0;
}
@Override
public boolean isHasIntegerValue() {
return scale >= 0;
}
@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();
case e:
return getExponent();
case c:
// Plural operand `c` is currently an alias for `e`.
return getExponent();
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 Math.max(0, -getLowerDisplayMagnitude() - exponent);
}
private int fractionCountWithoutTrailingZeros() {
return Math.max(-scale - exponent, 0);
}
@Override
public boolean isNegative() {
return (flags & NEGATIVE_FLAG) != 0;
}
@Override
public Signum signum() {
boolean isZero = (isZeroish() && !isInfinite());
boolean isNeg = isNegative();
if (isZero && isNeg) {
return Signum.NEG_ZERO;
} else if (isZero) {
return Signum.POS_ZERO;
} else if (isNeg) {
return Signum.NEG;
} else {
return Signum.POS;
}
}
@Override
public boolean isInfinite() {
return (flags & INFINITY_FLAG) != 0;
}
@Override
public boolean isNaN() {
return (flags & NAN_FLAG) != 0;
}
@Override
public boolean isZeroish() {
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;
// The sign bit is the top bit in both double and long, so we can
// get the long bits for the double and compare it to zero to check
// the sign of the double.
if (Double.doubleToRawLongBits(n) < 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;
}
if (exponent == -1023 || exponent == 1024) {
// The extreme values of exponent are special; use slow path.
convertToAccurateDouble();
return;
}
// 3.3219... is log2(10)
int fracLength = (int) ((52 - exponent) / 3.32192809488736234787031942948939017586);
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;
}
@Override
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 = exponent + scale + precision - 1;
if (truncateIfOverflow) {
upperMagnitude = Math.min(upperMagnitude, 17);
}
for (int magnitude = upperMagnitude; magnitude >= 0; magnitude--) {
result = result * 10 + getDigitPos(magnitude - scale - exponent);
}
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.
* Note: this method incorporates the value of {@code exponent}
* (for cases such as compact notation) to return the proper long value
* represented by the result.
*/
public long toFractionLong(boolean includeTrailingZeros) {
long result = 0L;
int magnitude = -1 - exponent;
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 (isInfinite() || isNaN()) {
return false;
}
if (isZeroish()) {
return true;
}
if (exponent + 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.parseDouble(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) {
assert trailingDigit == 0;
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);
// If the quantity was set to 0, we may need to restore a digit.
if (precision == 0) {
precision = 1;
}
// 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.
*
*
* 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() {
StringBuilder sb = new StringBuilder();
toPlainString(sb);
return sb.toString();
}
public void toPlainString(StringBuilder result) {
assert(!isApproximate);
if (isNegative()) {
result.append('-');
}
if (precision == 0) {
result.append('0');
return;
}
int upper = scale + precision + exponent - 1;
int lower = scale + exponent;
if (upper < lReqPos - 1) {
upper = lReqPos - 1;
}
if (lower > rReqPos) {
lower = rReqPos;
}
int p = upper;
if (p < 0) {
result.append('0');
}
for (; p >= 0; p--) {
result.append((char) ('0' + getDigitPos(p - scale - exponent)));
}
if (lower < 0) {
result.append('.');
}
for(; p >= lower; p--) {
result.append((char) ('0' + getDigitPos(p - scale - exponent)));
}
}
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 + exponent;
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 String toExponentString() {
StringBuilder sb = new StringBuilder();
toExponentString(sb);
return sb.toString();
}
private void toExponentString(StringBuilder result) {
assert(!isApproximate);
if (isNegative()) {
result.append('-');
}
int upper = scale + precision - 1;
int lower = scale;
if (upper < lReqPos - 1) {
upper = lReqPos - 1;
}
if (lower > rReqPos) {
lower = rReqPos;
}
int p = upper;
if (p < 0) {
result.append('0');
}
for (; p >= 0; p--) {
result.append((char) ('0' + getDigitPos(p - scale)));
}
if (lower < 0) {
result.append('.');
}
for(; p >= lower; p--) {
result.append((char) ('0' + getDigitPos(p - scale)));
}
if (exponent != 0) {
result.append('c');
result.append(exponent);
}
}
@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, and to ensure
* that the precision field is updated to reflect the correct number of digits if a
* nonzero digit is added to the decimal.
*
* @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, exponent, 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.
*
*
* 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();
}