|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
/* |
|
* (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved |
|
* (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved |
|
* |
|
* The original version of this source code and documentation is copyrighted |
|
* and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These |
|
* materials are provided under terms of a License Agreement between Taligent |
|
* and Sun. This technology is protected by multiple US and International |
|
* patents. This notice and attribution to Taligent may not be removed. |
|
* Taligent is a registered trademark of Taligent, Inc. |
|
* |
|
*/ |
|
|
|
package java.text; |
|
|
|
import java.math.BigDecimal; |
|
import java.math.BigInteger; |
|
import java.math.RoundingMode; |
|
import sun.misc.FloatingDecimal; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
final class DigitList implements Cloneable { |
|
/** |
|
* The maximum number of significant digits in an IEEE 754 double, that |
|
* is, in a Java double. This must not be increased, or garbage digits |
|
* will be generated, and should not be decreased, or accuracy will be lost. |
|
*/ |
|
public static final int MAX_COUNT = 19; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public int decimalAt = 0; |
|
public int count = 0; |
|
public char[] digits = new char[MAX_COUNT]; |
|
|
|
private char[] data; |
|
private RoundingMode roundingMode = RoundingMode.HALF_EVEN; |
|
private boolean isNegative = false; |
|
|
|
|
|
|
|
*/ |
|
boolean isZero() { |
|
for (int i=0; i < count; ++i) { |
|
if (digits[i] != '0') { |
|
return false; |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
|
|
|
|
*/ |
|
void setRoundingMode(RoundingMode r) { |
|
roundingMode = r; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public void clear () { |
|
decimalAt = 0; |
|
count = 0; |
|
} |
|
|
|
|
|
|
|
*/ |
|
public void append(char digit) { |
|
if (count == digits.length) { |
|
char[] data = new char[count + 100]; |
|
System.arraycopy(digits, 0, data, 0, count); |
|
digits = data; |
|
} |
|
digits[count++] = digit; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public final double getDouble() { |
|
if (count == 0) { |
|
return 0.0; |
|
} |
|
|
|
StringBuffer temp = getStringBuffer(); |
|
temp.append('.'); |
|
temp.append(digits, 0, count); |
|
temp.append('E'); |
|
temp.append(decimalAt); |
|
return Double.parseDouble(temp.toString()); |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public final long getLong() { |
|
// for now, simple implementation; later, do proper IEEE native stuff |
|
|
|
if (count == 0) { |
|
return 0; |
|
} |
|
|
|
// We have to check for this, because this is the one NEGATIVE value |
|
// we represent. If we tried to just pass the digits off to parseLong, |
|
|
|
if (isLongMIN_VALUE()) { |
|
return Long.MIN_VALUE; |
|
} |
|
|
|
StringBuffer temp = getStringBuffer(); |
|
temp.append(digits, 0, count); |
|
for (int i = count; i < decimalAt; ++i) { |
|
temp.append('0'); |
|
} |
|
return Long.parseLong(temp.toString()); |
|
} |
|
|
|
public final BigDecimal getBigDecimal() { |
|
if (count == 0) { |
|
if (decimalAt == 0) { |
|
return BigDecimal.ZERO; |
|
} else { |
|
return new BigDecimal("0E" + decimalAt); |
|
} |
|
} |
|
|
|
if (decimalAt == count) { |
|
return new BigDecimal(digits, 0, count); |
|
} else { |
|
return new BigDecimal(digits, 0, count).scaleByPowerOfTen(decimalAt - count); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
boolean fitsIntoLong(boolean isPositive, boolean ignoreNegativeZero) { |
|
// Figure out if the result will fit in a long. We have to |
|
// first look for nonzero digits after the decimal point; |
|
// then check the size. If the digit count is 18 or less, then |
|
// the value can definitely be represented as a long. If it is 19 |
|
// then it may be too large. |
|
|
|
|
|
while (count > 0 && digits[count - 1] == '0') { |
|
--count; |
|
} |
|
|
|
if (count == 0) { |
|
// Positive zero fits into a long, but negative zero can only |
|
|
|
return isPositive || ignoreNegativeZero; |
|
} |
|
|
|
if (decimalAt < count || decimalAt > MAX_COUNT) { |
|
return false; |
|
} |
|
|
|
if (decimalAt < MAX_COUNT) return true; |
|
|
|
// At this point we have decimalAt == count, and count == MAX_COUNT. |
|
// The number will overflow if it is larger than 9223372036854775807 |
|
|
|
for (int i=0; i<count; ++i) { |
|
char dig = digits[i], max = LONG_MIN_REP[i]; |
|
if (dig > max) return false; |
|
if (dig < max) return true; |
|
} |
|
|
|
// At this point the first count digits match. If decimalAt is less |
|
|
|
if (count < decimalAt) return true; |
|
|
|
// Now we have a representation of Long.MIN_VALUE, without the leading |
|
// negative sign. If this represents a positive value, then it does |
|
|
|
return !isPositive; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
final void set(boolean isNegative, double source, int maximumFractionDigits) { |
|
set(isNegative, source, maximumFractionDigits, true); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
final void set(boolean isNegative, double source, int maximumDigits, boolean fixedPoint) { |
|
|
|
FloatingDecimal.BinaryToASCIIConverter fdConverter = FloatingDecimal.getBinaryToASCIIConverter(source); |
|
boolean hasBeenRoundedUp = fdConverter.digitsRoundedUp(); |
|
boolean valueExactAsDecimal = fdConverter.decimalDigitsExact(); |
|
assert !fdConverter.isExceptional(); |
|
String digitsString = fdConverter.toJavaFormatString(); |
|
|
|
set(isNegative, digitsString, |
|
hasBeenRoundedUp, valueExactAsDecimal, |
|
maximumDigits, fixedPoint); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private void set(boolean isNegative, String s, |
|
boolean roundedUp, boolean valueExactAsDecimal, |
|
int maximumDigits, boolean fixedPoint) { |
|
|
|
this.isNegative = isNegative; |
|
int len = s.length(); |
|
char[] source = getDataChars(len); |
|
s.getChars(0, len, source, 0); |
|
|
|
decimalAt = -1; |
|
count = 0; |
|
int exponent = 0; |
|
// Number of zeros between decimal point and first non-zero digit after |
|
|
|
int leadingZerosAfterDecimal = 0; |
|
boolean nonZeroDigitSeen = false; |
|
|
|
for (int i = 0; i < len; ) { |
|
char c = source[i++]; |
|
if (c == '.') { |
|
decimalAt = count; |
|
} else if (c == 'e' || c == 'E') { |
|
exponent = parseInt(source, i, len); |
|
break; |
|
} else { |
|
if (!nonZeroDigitSeen) { |
|
nonZeroDigitSeen = (c != '0'); |
|
if (!nonZeroDigitSeen && decimalAt != -1) |
|
++leadingZerosAfterDecimal; |
|
} |
|
if (nonZeroDigitSeen) { |
|
digits[count++] = c; |
|
} |
|
} |
|
} |
|
if (decimalAt == -1) { |
|
decimalAt = count; |
|
} |
|
if (nonZeroDigitSeen) { |
|
decimalAt += exponent - leadingZerosAfterDecimal; |
|
} |
|
|
|
if (fixedPoint) { |
|
// The negative of the exponent represents the number of leading |
|
// zeros between the decimal and the first non-zero digit, for |
|
// a value < 0.1 (e.g., for 0.00123, -decimalAt == 2). If this |
|
// is more than the maximum fraction digits, then we have an underflow |
|
|
|
if (-decimalAt > maximumDigits) { |
|
// Handle an underflow to zero when we round something like |
|
|
|
count = 0; |
|
return; |
|
} else if (-decimalAt == maximumDigits) { |
|
// If we round 0.0009 to 3 fractional digits, then we have to |
|
|
|
if (shouldRoundUp(0, roundedUp, valueExactAsDecimal)) { |
|
count = 1; |
|
++decimalAt; |
|
digits[0] = '1'; |
|
} else { |
|
count = 0; |
|
} |
|
return; |
|
} |
|
// else fall through |
|
} |
|
|
|
|
|
while (count > 1 && digits[count - 1] == '0') { |
|
--count; |
|
} |
|
|
|
// Eliminate digits beyond maximum digits to be displayed. |
|
|
|
round(fixedPoint ? (maximumDigits + decimalAt) : maximumDigits, |
|
roundedUp, valueExactAsDecimal); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private final void round(int maximumDigits, |
|
boolean alreadyRounded, |
|
boolean valueExactAsDecimal) { |
|
// Eliminate digits beyond maximum digits to be displayed. |
|
|
|
if (maximumDigits >= 0 && maximumDigits < count) { |
|
if (shouldRoundUp(maximumDigits, alreadyRounded, valueExactAsDecimal)) { |
|
// Rounding up involved incrementing digits from LSD to MSD. |
|
// In most cases this is simple, but in a worst case situation |
|
|
|
for (;;) { |
|
--maximumDigits; |
|
if (maximumDigits < 0) { |
|
// We have all 9's, so we increment to a single digit |
|
|
|
digits[0] = '1'; |
|
++decimalAt; |
|
maximumDigits = 0; |
|
break; |
|
} |
|
|
|
++digits[maximumDigits]; |
|
if (digits[maximumDigits] <= '9') break; |
|
// digits[maximumDigits] = '0'; // Unnecessary since we'll truncate this |
|
} |
|
++maximumDigits; |
|
} |
|
count = maximumDigits; |
|
|
|
|
|
while (count > 1 && digits[count-1] == '0') { |
|
--count; |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private boolean shouldRoundUp(int maximumDigits, |
|
boolean alreadyRounded, |
|
boolean valueExactAsDecimal) { |
|
if (maximumDigits < count) { |
|
/* |
|
* To avoid erroneous double-rounding or truncation when converting |
|
* a binary double value to text, information about the exactness |
|
* of the conversion result in FloatingDecimal, as well as any |
|
* rounding done, is needed in this class. |
|
* |
|
* - For the HALF_DOWN, HALF_EVEN, HALF_UP rounding rules below: |
|
* In the case of formating float or double, We must take into |
|
* account what FloatingDecimal has done in the binary to decimal |
|
* conversion. |
|
* |
|
* Considering the tie cases, FloatingDecimal may round up the |
|
* value (returning decimal digits equal to tie when it is below), |
|
* or "truncate" the value to the tie while value is above it, |
|
* or provide the exact decimal digits when the binary value can be |
|
* converted exactly to its decimal representation given formating |
|
* rules of FloatingDecimal ( we have thus an exact decimal |
|
* representation of the binary value). |
|
* |
|
* - If the double binary value was converted exactly as a decimal |
|
* value, then DigitList code must apply the expected rounding |
|
* rule. |
|
* |
|
* - If FloatingDecimal already rounded up the decimal value, |
|
* DigitList should neither round up the value again in any of |
|
* the three rounding modes above. |
|
* |
|
* - If FloatingDecimal has truncated the decimal value to |
|
* an ending '5' digit, DigitList should round up the value in |
|
* all of the three rounding modes above. |
|
* |
|
* |
|
* This has to be considered only if digit at maximumDigits index |
|
* is exactly the last one in the set of digits, otherwise there are |
|
* remaining digits after that position and we don't have to consider |
|
* what FloatingDecimal did. |
|
* |
|
* - Other rounding modes are not impacted by these tie cases. |
|
* |
|
* - For other numbers that are always converted to exact digits |
|
* (like BigInteger, Long, ...), the passed alreadyRounded boolean |
|
* have to be set to false, and valueExactAsDecimal has to be set to |
|
* true in the upper DigitList call stack, providing the right state |
|
* for those situations.. |
|
*/ |
|
|
|
switch(roundingMode) { |
|
case UP: |
|
for (int i=maximumDigits; i<count; ++i) { |
|
if (digits[i] != '0') { |
|
return true; |
|
} |
|
} |
|
break; |
|
case DOWN: |
|
break; |
|
case CEILING: |
|
for (int i=maximumDigits; i<count; ++i) { |
|
if (digits[i] != '0') { |
|
return !isNegative; |
|
} |
|
} |
|
break; |
|
case FLOOR: |
|
for (int i=maximumDigits; i<count; ++i) { |
|
if (digits[i] != '0') { |
|
return isNegative; |
|
} |
|
} |
|
break; |
|
case HALF_UP: |
|
case HALF_DOWN: |
|
if (digits[maximumDigits] > '5') { |
|
|
|
return true; |
|
} else if (digits[maximumDigits] == '5') { |
|
|
|
if (maximumDigits != (count - 1)) { |
|
|
|
return true; |
|
} else { |
|
|
|
if (valueExactAsDecimal) { |
|
// Exact binary representation. On the tie. |
|
|
|
return roundingMode == RoundingMode.HALF_UP; |
|
} else { |
|
// Not an exact binary representation. |
|
// Digit sequence either rounded up or truncated. |
|
|
|
return !alreadyRounded; |
|
} |
|
} |
|
} |
|
// Digit at rounding position is < '5' ==> no round up. |
|
|
|
break; |
|
case HALF_EVEN: |
|
|
|
if (digits[maximumDigits] > '5') { |
|
return true; |
|
} else if (digits[maximumDigits] == '5' ) { |
|
if (maximumDigits == (count - 1)) { |
|
|
|
if (alreadyRounded) |
|
// If FloatingDecimal rounded up (value was below tie), |
|
|
|
return false; |
|
|
|
if (!valueExactAsDecimal) |
|
// Otherwise if the digits don't represent exact value, |
|
// value was above tie and FloatingDecimal truncated |
|
|
|
return true; |
|
else { |
|
// This is an exact tie value, and FloatingDecimal |
|
// provided all of the exact digits. We thus apply |
|
|
|
return ((maximumDigits > 0) && |
|
(digits[maximumDigits-1] % 2 != 0)); |
|
} |
|
} else { |
|
|
|
for (int i=maximumDigits+1; i<count; ++i) { |
|
if (digits[i] != '0') |
|
return true; |
|
} |
|
} |
|
} |
|
break; |
|
case UNNECESSARY: |
|
for (int i=maximumDigits; i<count; ++i) { |
|
if (digits[i] != '0') { |
|
throw new ArithmeticException( |
|
"Rounding needed with the rounding mode being set to RoundingMode.UNNECESSARY"); |
|
} |
|
} |
|
break; |
|
default: |
|
assert false; |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
|
|
|
|
*/ |
|
final void set(boolean isNegative, long source) { |
|
set(isNegative, source, 0); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
final void set(boolean isNegative, long source, int maximumDigits) { |
|
this.isNegative = isNegative; |
|
|
|
// This method does not expect a negative number. However, |
|
// "source" can be a Long.MIN_VALUE (-9223372036854775808), |
|
// if the number being formatted is a Long.MIN_VALUE. In that |
|
// case, it will be formatted as -Long.MIN_VALUE, a number |
|
// which is outside the legal range of a long, but which can |
|
|
|
if (source <= 0) { |
|
if (source == Long.MIN_VALUE) { |
|
decimalAt = count = MAX_COUNT; |
|
System.arraycopy(LONG_MIN_REP, 0, digits, 0, count); |
|
} else { |
|
decimalAt = count = 0; |
|
} |
|
} else { |
|
// Rewritten to improve performance. I used to call |
|
|
|
int left = MAX_COUNT; |
|
int right; |
|
while (source > 0) { |
|
digits[--left] = (char)('0' + (source % 10)); |
|
source /= 10; |
|
} |
|
decimalAt = MAX_COUNT - left; |
|
// Don't copy trailing zeros. We are guaranteed that there is at |
|
|
|
for (right = MAX_COUNT - 1; digits[right] == '0'; --right) |
|
; |
|
count = right - left + 1; |
|
System.arraycopy(digits, left, digits, 0, count); |
|
} |
|
if (maximumDigits > 0) round(maximumDigits, false, true); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
final void set(boolean isNegative, BigDecimal source, int maximumDigits, boolean fixedPoint) { |
|
String s = source.toString(); |
|
extendDigits(s.length()); |
|
|
|
set(isNegative, s, |
|
false, true, |
|
maximumDigits, fixedPoint); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
final void set(boolean isNegative, BigInteger source, int maximumDigits) { |
|
this.isNegative = isNegative; |
|
String s = source.toString(); |
|
int len = s.length(); |
|
extendDigits(len); |
|
s.getChars(0, len, digits, 0); |
|
|
|
decimalAt = len; |
|
int right; |
|
for (right = len - 1; right >= 0 && digits[right] == '0'; --right) |
|
; |
|
count = right + 1; |
|
|
|
if (maximumDigits > 0) { |
|
round(maximumDigits, false, true); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
public boolean equals(Object obj) { |
|
if (this == obj) |
|
return true; |
|
if (!(obj instanceof DigitList)) |
|
return false; |
|
DigitList other = (DigitList) obj; |
|
if (count != other.count || |
|
decimalAt != other.decimalAt) |
|
return false; |
|
for (int i = 0; i < count; i++) |
|
if (digits[i] != other.digits[i]) |
|
return false; |
|
return true; |
|
} |
|
|
|
|
|
|
|
*/ |
|
public int hashCode() { |
|
int hashcode = decimalAt; |
|
|
|
for (int i = 0; i < count; i++) { |
|
hashcode = hashcode * 37 + digits[i]; |
|
} |
|
|
|
return hashcode; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public Object clone() { |
|
try { |
|
DigitList other = (DigitList) super.clone(); |
|
char[] newDigits = new char[digits.length]; |
|
System.arraycopy(digits, 0, newDigits, 0, digits.length); |
|
other.digits = newDigits; |
|
other.tempBuffer = null; |
|
return other; |
|
} catch (CloneNotSupportedException e) { |
|
throw new InternalError(e); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private boolean isLongMIN_VALUE() { |
|
if (decimalAt != count || count != MAX_COUNT) { |
|
return false; |
|
} |
|
|
|
for (int i = 0; i < count; ++i) { |
|
if (digits[i] != LONG_MIN_REP[i]) return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
private static final int parseInt(char[] str, int offset, int strLen) { |
|
char c; |
|
boolean positive = true; |
|
if ((c = str[offset]) == '-') { |
|
positive = false; |
|
offset++; |
|
} else if (c == '+') { |
|
offset++; |
|
} |
|
|
|
int value = 0; |
|
while (offset < strLen) { |
|
c = str[offset++]; |
|
if (c >= '0' && c <= '9') { |
|
value = value * 10 + (c - '0'); |
|
} else { |
|
break; |
|
} |
|
} |
|
return positive ? value : -value; |
|
} |
|
|
|
|
|
private static final char[] LONG_MIN_REP = "9223372036854775808".toCharArray(); |
|
|
|
public String toString() { |
|
if (isZero()) { |
|
return "0"; |
|
} |
|
StringBuffer buf = getStringBuffer(); |
|
buf.append("0."); |
|
buf.append(digits, 0, count); |
|
buf.append("x10^"); |
|
buf.append(decimalAt); |
|
return buf.toString(); |
|
} |
|
|
|
private StringBuffer tempBuffer; |
|
|
|
private StringBuffer getStringBuffer() { |
|
if (tempBuffer == null) { |
|
tempBuffer = new StringBuffer(MAX_COUNT); |
|
} else { |
|
tempBuffer.setLength(0); |
|
} |
|
return tempBuffer; |
|
} |
|
|
|
private void extendDigits(int len) { |
|
if (len > digits.length) { |
|
digits = new char[len]; |
|
} |
|
} |
|
|
|
private final char[] getDataChars(int length) { |
|
if (data == null || data.length < length) { |
|
data = new char[length]; |
|
} |
|
return data; |
|
} |
|
} |