You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
685 lines
21 KiB
685 lines
21 KiB
package com.ibm.icu.text;
|
|
|
|
import java.io.IOException;
|
|
import java.io.NotSerializableException;
|
|
import java.io.ObjectInputStream;
|
|
import java.io.ObjectOutputStream;
|
|
import java.util.Locale;
|
|
|
|
import com.ibm.icu.text.PluralRules.IFixedDecimal;
|
|
import com.ibm.icu.text.PluralRules.Operand;
|
|
|
|
/**
|
|
* @internal CLDR
|
|
* @deprecated This API is ICU internal only.
|
|
*/
|
|
@Deprecated
|
|
public class FixedDecimal extends Number implements Comparable<FixedDecimal>, IFixedDecimal {
|
|
private static final long serialVersionUID = -4756200506571685661L;
|
|
|
|
final double source;
|
|
|
|
final int visibleDecimalDigitCount;
|
|
|
|
final int visibleDecimalDigitCountWithoutTrailingZeros;
|
|
|
|
final long decimalDigits;
|
|
|
|
final long decimalDigitsWithoutTrailingZeros;
|
|
|
|
final long integerValue;
|
|
|
|
final boolean hasIntegerValue;
|
|
|
|
final boolean isNegative;
|
|
|
|
final int exponent;
|
|
|
|
private final int baseFactor;
|
|
|
|
/**
|
|
* @internal CLDR
|
|
* @deprecated This API is ICU internal only.
|
|
*/
|
|
@Deprecated
|
|
public double getSource() {
|
|
return source;
|
|
}
|
|
|
|
/**
|
|
* @internal CLDR
|
|
* @deprecated This API is ICU internal only.
|
|
*/
|
|
@Deprecated
|
|
public int getVisibleDecimalDigitCount() {
|
|
return visibleDecimalDigitCount;
|
|
}
|
|
|
|
/**
|
|
* @internal CLDR
|
|
* @deprecated This API is ICU internal only.
|
|
*/
|
|
@Deprecated
|
|
public int getVisibleDecimalDigitCountWithoutTrailingZeros() {
|
|
return visibleDecimalDigitCountWithoutTrailingZeros;
|
|
}
|
|
|
|
/**
|
|
* @internal CLDR
|
|
* @deprecated This API is ICU internal only.
|
|
*/
|
|
@Deprecated
|
|
public long getDecimalDigits() {
|
|
return decimalDigits;
|
|
}
|
|
|
|
/**
|
|
* @internal CLDR
|
|
* @deprecated This API is ICU internal only.
|
|
*/
|
|
@Deprecated
|
|
public long getDecimalDigitsWithoutTrailingZeros() {
|
|
return decimalDigitsWithoutTrailingZeros;
|
|
}
|
|
|
|
/**
|
|
* @internal CLDR
|
|
* @deprecated This API is ICU internal only.
|
|
*/
|
|
@Deprecated
|
|
public long getIntegerValue() {
|
|
return integerValue;
|
|
}
|
|
|
|
/**
|
|
* @internal CLDR
|
|
* @deprecated This API is ICU internal only.
|
|
*/
|
|
@Deprecated
|
|
public boolean isHasIntegerValue() {
|
|
return hasIntegerValue;
|
|
}
|
|
|
|
/**
|
|
* @internal CLDR
|
|
* @deprecated This API is ICU internal only.
|
|
*/
|
|
@Deprecated
|
|
public boolean isNegative() {
|
|
return isNegative;
|
|
}
|
|
|
|
/**
|
|
* @internal CLDR
|
|
* @deprecated This API is ICU internal only.
|
|
*/
|
|
@Deprecated
|
|
public int getBaseFactor() {
|
|
return baseFactor;
|
|
}
|
|
|
|
static final long MAX = (long)1E18;
|
|
|
|
/**
|
|
* @internal CLDR
|
|
* @deprecated This API is ICU internal only.
|
|
* @param n is the original number
|
|
* @param v number of digits to the right of the decimal place. e.g 1.00 = 2 25. = 0
|
|
* @param f Corresponds to f in the plural rules grammar.
|
|
* The digits to the right of the decimal place as an integer. e.g 1.10 = 10
|
|
* @param e Suppressed exponent for scientific and compact notation
|
|
*/
|
|
@Deprecated
|
|
public FixedDecimal(double n, int v, long f, int e) {
|
|
isNegative = n < 0;
|
|
source = isNegative ? -n : n;
|
|
visibleDecimalDigitCount = v;
|
|
decimalDigits = f;
|
|
integerValue = n > MAX
|
|
? MAX
|
|
: (long)n;
|
|
exponent = e;
|
|
hasIntegerValue = source == integerValue;
|
|
// check values. TODO make into unit test.
|
|
//
|
|
// long visiblePower = (int) Math.pow(10, v);
|
|
// if (fractionalDigits > visiblePower) {
|
|
// throw new IllegalArgumentException();
|
|
// }
|
|
// double fraction = intValue + (fractionalDigits / (double) visiblePower);
|
|
// if (fraction != source) {
|
|
// double diff = Math.abs(fraction - source)/(Math.abs(fraction) + Math.abs(source));
|
|
// if (diff > 0.00000001d) {
|
|
// throw new IllegalArgumentException();
|
|
// }
|
|
// }
|
|
if (f == 0) {
|
|
decimalDigitsWithoutTrailingZeros = 0;
|
|
visibleDecimalDigitCountWithoutTrailingZeros = 0;
|
|
} else {
|
|
long fdwtz = f;
|
|
int trimmedCount = v;
|
|
while ((fdwtz%10) == 0) {
|
|
fdwtz /= 10;
|
|
--trimmedCount;
|
|
}
|
|
decimalDigitsWithoutTrailingZeros = fdwtz;
|
|
visibleDecimalDigitCountWithoutTrailingZeros = trimmedCount;
|
|
}
|
|
baseFactor = (int) Math.pow(10, v);
|
|
}
|
|
|
|
/**
|
|
* @internal CLDR
|
|
* @deprecated This API is ICU internal only.
|
|
*/
|
|
@Deprecated
|
|
public FixedDecimal(double n, int v, long f) {
|
|
this(n, v, f, 0);
|
|
}
|
|
|
|
/**
|
|
* @internal CLDR
|
|
* @deprecated This API is ICU internal only.
|
|
*/
|
|
@Deprecated
|
|
public static FixedDecimal createWithExponent(double n, int v, int e) {
|
|
return new FixedDecimal(n,v,getFractionalDigits(n, v), e);
|
|
}
|
|
|
|
/**
|
|
* @internal CLDR
|
|
* @deprecated This API is ICU internal only.
|
|
*/
|
|
@Deprecated
|
|
public FixedDecimal(double n, int v) {
|
|
this(n,v,getFractionalDigits(n, v));
|
|
}
|
|
|
|
private static int getFractionalDigits(double n, int v) {
|
|
if (v == 0) {
|
|
return 0;
|
|
} else {
|
|
if (n < 0) {
|
|
n = -n;
|
|
}
|
|
int baseFactor = (int) Math.pow(10, v);
|
|
long scaled = Math.round(n * baseFactor);
|
|
return (int) (scaled % baseFactor);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @internal CLDR
|
|
* @deprecated This API is ICU internal only.
|
|
*/
|
|
@Deprecated
|
|
public FixedDecimal(double n) {
|
|
this(n, decimals(n));
|
|
}
|
|
|
|
/**
|
|
* @internal CLDR
|
|
* @deprecated This API is ICU internal only.
|
|
*/
|
|
@Deprecated
|
|
public FixedDecimal(long n) {
|
|
this(n,0);
|
|
}
|
|
|
|
private static final long MAX_INTEGER_PART = 1000000000;
|
|
/**
|
|
* Return a guess as to the number of decimals that would be displayed. This is only a guess; callers should
|
|
* always supply the decimals explicitly if possible. Currently, it is up to 6 decimals (without trailing zeros).
|
|
* Returns 0 for infinities and nans.
|
|
* @internal CLDR
|
|
* @deprecated This API is ICU internal only.
|
|
*
|
|
*/
|
|
@Deprecated
|
|
public static int decimals(double n) {
|
|
// Ugly...
|
|
if (Double.isInfinite(n) || Double.isNaN(n)) {
|
|
return 0;
|
|
}
|
|
if (n < 0) {
|
|
n = -n;
|
|
}
|
|
if (n == Math.floor(n)) {
|
|
return 0;
|
|
}
|
|
if (n < MAX_INTEGER_PART) {
|
|
long temp = (long)(n * 1000000) % 1000000; // get 6 decimals
|
|
for (int mask = 10, digits = 6; digits > 0; mask *= 10, --digits) {
|
|
if ((temp % mask) != 0) {
|
|
return digits;
|
|
}
|
|
}
|
|
return 0;
|
|
} else {
|
|
String buf = String.format(Locale.ENGLISH, "%1.15e", n);
|
|
int ePos = buf.lastIndexOf('e');
|
|
int expNumPos = ePos + 1;
|
|
if (buf.charAt(expNumPos) == '+') {
|
|
expNumPos++;
|
|
}
|
|
String exponentStr = buf.substring(expNumPos);
|
|
int exponent = Integer.parseInt(exponentStr);
|
|
int numFractionDigits = ePos - 2 - exponent;
|
|
if (numFractionDigits < 0) {
|
|
return 0;
|
|
}
|
|
for (int i=ePos-1; numFractionDigits > 0; --i) {
|
|
if (buf.charAt(i) != '0') {
|
|
break;
|
|
}
|
|
--numFractionDigits;
|
|
}
|
|
return numFractionDigits;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @internal CLDR
|
|
* @deprecated This API is ICU internal only
|
|
*/
|
|
@Deprecated
|
|
private FixedDecimal (FixedDecimal other) {
|
|
// Ugly, but necessary, because constructors must only call other
|
|
// constructors in the first line of the body, and
|
|
// FixedDecimal(String) was refactored to support exponents.
|
|
this.source = other.source;
|
|
this.visibleDecimalDigitCount = other.visibleDecimalDigitCount;
|
|
this.visibleDecimalDigitCountWithoutTrailingZeros =
|
|
other.visibleDecimalDigitCountWithoutTrailingZeros;
|
|
this.decimalDigits = other.decimalDigits;
|
|
this.decimalDigitsWithoutTrailingZeros =
|
|
other.decimalDigitsWithoutTrailingZeros;
|
|
this.integerValue = other.integerValue;
|
|
this.hasIntegerValue = other.hasIntegerValue;
|
|
this.isNegative = other.isNegative;
|
|
this.exponent = other.exponent;
|
|
this.baseFactor = other.baseFactor;
|
|
}
|
|
|
|
/**
|
|
* @internal CLDR
|
|
* @deprecated This API is ICU internal only.
|
|
*/
|
|
@Deprecated
|
|
public FixedDecimal (String n) {
|
|
// Ugly, but for samples we don't care.
|
|
this(parseDecimalSampleRangeNumString(n));
|
|
}
|
|
|
|
// /**
|
|
// * @internal CLDR
|
|
// * @deprecated This API is ICU internal only
|
|
// */
|
|
// @Deprecated
|
|
// private static FixedDecimal parseDecimalSampleRangeNumString(String num) {
|
|
// if (num.contains("e")) {
|
|
// int ePos = num.lastIndexOf('e');
|
|
// int expNumPos = ePos + 1;
|
|
// String exponentStr = num.substring(expNumPos);
|
|
// int exponent = Integer.parseInt(exponentStr);
|
|
// String fractionStr = num.substring(0, ePos);
|
|
// return FixedDecimal.createWithExponent(
|
|
// Double.parseDouble(fractionStr),
|
|
// getVisibleFractionCount(fractionStr),
|
|
// exponent);
|
|
// } else {
|
|
// return new FixedDecimal(Double.parseDouble(num), getVisibleFractionCount(num));
|
|
// }
|
|
// }
|
|
|
|
// The value of n needs to take the exponent into account
|
|
public static FixedDecimal parseDecimalSampleRangeNumString(String num) {
|
|
double n;
|
|
int v;
|
|
int exponent = 0;
|
|
String fractionStr = num; // default
|
|
if (num.contains("e")) {
|
|
int ePos = num.lastIndexOf('e');
|
|
int expNumPos = ePos + 1;
|
|
String exponentStr = num.substring(expNumPos);
|
|
exponent = Integer.parseInt(exponentStr);
|
|
fractionStr = num.substring(0, ePos);
|
|
|
|
// now adjust the fraction string according to the exponent
|
|
// not the most efficient, but more reliable code for testing
|
|
if (exponent != 0) {
|
|
int decimalPos = fractionStr.indexOf('.');
|
|
int decimalCount = 0;
|
|
String integerPart = fractionStr;
|
|
String fractionPart = "";
|
|
if (decimalPos >= 0) {
|
|
decimalCount = fractionStr.length() - decimalPos - 1;
|
|
integerPart = fractionStr.substring(0,decimalPos);
|
|
fractionPart = fractionStr.substring(decimalPos+1);
|
|
}
|
|
|
|
if (decimalCount == exponent) { // 2.123e3 => 2123
|
|
fractionStr = integerPart + fractionPart;
|
|
} else if (decimalCount > exponent) { // 2.1234e3 => 2123.4
|
|
fractionStr = integerPart + fractionPart.substring(0,exponent) + "." + fractionPart.substring(exponent);
|
|
} else { // decimalCount < exponent // // 2.1e3 => 2100
|
|
fractionStr = integerPart + padEnd(fractionPart, exponent, '0');
|
|
}
|
|
}
|
|
}
|
|
n = Double.parseDouble(fractionStr);
|
|
v = getVisibleFractionCount(fractionStr);
|
|
return new FixedDecimal(n, v, getFractionalDigits(n, v), exponent);
|
|
}
|
|
|
|
|
|
private static String padEnd(String string, int minLength, char c) {
|
|
StringBuilder sb = new StringBuilder(minLength);
|
|
sb.append(string);
|
|
for (int i = string.length(); i < minLength; i++) {
|
|
sb.append(c);
|
|
}
|
|
return sb.toString();
|
|
}
|
|
|
|
public static int getVisibleFractionCount(String value) {
|
|
value = value.trim();
|
|
int decimalPos = value.indexOf('.') + 1;
|
|
if (decimalPos == 0) {
|
|
return 0;
|
|
} else {
|
|
return value.length() - decimalPos;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*
|
|
* @internal CLDR
|
|
* @deprecated This API is ICU internal only.
|
|
*/
|
|
@Override
|
|
@Deprecated
|
|
public double getPluralOperand(Operand operand) {
|
|
switch(operand) {
|
|
case n: return source;
|
|
case i: return integerValue;
|
|
case f: return decimalDigits;
|
|
case t: return decimalDigitsWithoutTrailingZeros;
|
|
case v: return visibleDecimalDigitCount;
|
|
case w: return visibleDecimalDigitCountWithoutTrailingZeros;
|
|
case e: return exponent;
|
|
default: return source;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @internal CLDR
|
|
* @deprecated This API is ICU internal only.
|
|
*/
|
|
@Deprecated
|
|
public static Operand getOperand(String t) {
|
|
return Operand.valueOf(t);
|
|
}
|
|
|
|
/**
|
|
* We're not going to care about NaN.
|
|
* @internal CLDR
|
|
* @deprecated This API is ICU internal only.
|
|
*/
|
|
@Override
|
|
@Deprecated
|
|
public int compareTo(FixedDecimal other) {
|
|
if (exponent != other.exponent) {
|
|
return exponent < other.exponent ? -1 : 1;
|
|
}
|
|
if (integerValue != other.integerValue) {
|
|
return integerValue < other.integerValue ? -1 : 1;
|
|
}
|
|
if (source != other.source) {
|
|
return source < other.source ? -1 : 1;
|
|
}
|
|
if (visibleDecimalDigitCount != other.visibleDecimalDigitCount) {
|
|
return visibleDecimalDigitCount < other.visibleDecimalDigitCount ? -1 : 1;
|
|
}
|
|
long diff = decimalDigits - other.decimalDigits;
|
|
if (diff != 0) {
|
|
return diff < 0 ? -1 : 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @internal CLDR
|
|
* @deprecated This API is ICU internal only.
|
|
*/
|
|
@Deprecated
|
|
@Override
|
|
public boolean equals(Object arg0) {
|
|
if (arg0 == null) {
|
|
return false;
|
|
}
|
|
if (arg0 == this) {
|
|
return true;
|
|
}
|
|
if (!(arg0 instanceof FixedDecimal)) {
|
|
return false;
|
|
}
|
|
FixedDecimal other = (FixedDecimal)arg0;
|
|
return source == other.source && visibleDecimalDigitCount == other.visibleDecimalDigitCount && decimalDigits == other.decimalDigits
|
|
&& exponent == other.exponent;
|
|
}
|
|
|
|
/**
|
|
* @internal CLDR
|
|
* @deprecated This API is ICU internal only.
|
|
*/
|
|
@Deprecated
|
|
@Override
|
|
public int hashCode() {
|
|
// TODO Auto-generated method stub
|
|
return (int)(decimalDigits + 37 * (visibleDecimalDigitCount + (int)(37 * source)));
|
|
}
|
|
|
|
public static String toSampleString(IFixedDecimal source) {
|
|
final double n = source.getPluralOperand(Operand.n);
|
|
final int exponent = (int) source.getPluralOperand(Operand.e);
|
|
final int visibleDecimalDigitCount = (int) source.getPluralOperand(Operand.v);
|
|
if (exponent == 0) {
|
|
return String.format(Locale.ROOT, "%." + visibleDecimalDigitCount + "f", n);
|
|
} else {
|
|
// we need to slide the exponent back
|
|
|
|
int fixedV = visibleDecimalDigitCount + exponent;
|
|
String baseString = String.format(Locale.ROOT, "%." + fixedV + "f",n/Math.pow(10,exponent));
|
|
|
|
// HACK
|
|
// However, we don't have enough information to round-trip if v == 0
|
|
// So in that case we choose the shortest form,
|
|
// so we have to have a hack to strip trailing fraction spaces.
|
|
if (visibleDecimalDigitCount == 0) {
|
|
for (int i = visibleDecimalDigitCount; i < fixedV; ++i) {
|
|
// TODO this code could and should be optimized, but for now...
|
|
if (baseString.endsWith("0")) {
|
|
baseString = baseString.substring(0,baseString.length()-1);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
if (baseString.endsWith(".")) {
|
|
baseString = baseString.substring(0,baseString.length()-1);
|
|
}
|
|
}
|
|
return baseString + "e" + exponent;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @internal CLDR
|
|
* @deprecated This API is ICU internal only.
|
|
*/
|
|
@Deprecated
|
|
@Override
|
|
public String toString() {
|
|
return toSampleString(this);
|
|
// if (exponent == 0) {
|
|
// return String.format(Locale.ROOT, "%." + visibleDecimalDigitCount + "f", source);
|
|
// } else {
|
|
// // we need to slide the exponent back
|
|
//
|
|
// int fixedV = visibleDecimalDigitCount + exponent;
|
|
// String baseString = String.format(Locale.ROOT, "%." + fixedV + "f", getSource()/Math.pow(10,exponent));
|
|
//
|
|
// // However, we don't have enough information to round-trip if v == 0
|
|
// // So in that case we choose the shortest form,
|
|
// // so we have to have a hack to strip trailing fraction spaces.
|
|
// if (visibleDecimalDigitCount == 0) {
|
|
// for (int i = visibleDecimalDigitCount; i < fixedV; ++i) {
|
|
// // TODO this code could and should be optimized, but for now...
|
|
// if (baseString.endsWith("0")) {
|
|
// baseString = baseString.substring(0,baseString.length()-1);
|
|
// continue;
|
|
// }
|
|
// break;
|
|
// }
|
|
// if (baseString.endsWith(".")) {
|
|
// baseString = baseString.substring(0,baseString.length()-1);
|
|
// }
|
|
// }
|
|
//
|
|
// return baseString + "e" + exponent;
|
|
// }
|
|
}
|
|
|
|
// // FixedDecimal.toString isn't working right.
|
|
// public String xtoString() {
|
|
// // we need to slide v up
|
|
// final int v = getVisibleDecimalDigitCount();
|
|
// final int exponent = getExponent();
|
|
// if (exponent == 0) {
|
|
// return String.format(Locale.ROOT, "%." + v + "f", getSource());
|
|
// }
|
|
// int fixedV = v + exponent;
|
|
// String baseString = String.format(Locale.ROOT, "%." + fixedV + "f", getSource()/Math.pow(10,exponent));
|
|
// // however, the format does not round trip.
|
|
// // so we have to have a hack to strip trailing fraction spaces.
|
|
// for (int i = v; i < fixedV; ++i) {
|
|
// // TODO this code could and should be optimized, but for now...
|
|
// if (baseString.endsWith("0")) {
|
|
// baseString = baseString.substring(0,baseString.length()-1);
|
|
// continue;
|
|
// }
|
|
// break;
|
|
// }
|
|
// if (baseString.endsWith(".")) {
|
|
// baseString = baseString.substring(0,baseString.length()-1);
|
|
// }
|
|
// return baseString + "e" + exponent;
|
|
// }
|
|
|
|
/**
|
|
* @internal CLDR
|
|
* @deprecated This API is ICU internal only.
|
|
*/
|
|
@Deprecated
|
|
public boolean hasIntegerValue() {
|
|
return hasIntegerValue;
|
|
}
|
|
|
|
/**
|
|
* @internal CLDR
|
|
* @deprecated This API is ICU internal only.
|
|
*/
|
|
@Deprecated
|
|
@Override
|
|
public int intValue() {
|
|
// TODO Auto-generated method stub
|
|
return (int) longValue();
|
|
}
|
|
|
|
/**
|
|
* @internal CLDR
|
|
* @deprecated This API is ICU internal only.
|
|
*/
|
|
@Deprecated
|
|
@Override
|
|
public long longValue() {
|
|
if (exponent == 0) {
|
|
return integerValue;
|
|
} else {
|
|
return (long) (Math.pow(10, exponent) * integerValue);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @internal CLDR
|
|
* @deprecated This API is ICU internal only.
|
|
*/
|
|
@Deprecated
|
|
@Override
|
|
public float floatValue() {
|
|
return (float) (source * Math.pow(10, exponent));
|
|
}
|
|
|
|
/**
|
|
* @internal CLDR
|
|
* @deprecated This API is ICU internal only.
|
|
*/
|
|
@Deprecated
|
|
@Override
|
|
public double doubleValue() {
|
|
return (isNegative ? -source : source);
|
|
}
|
|
|
|
/**
|
|
* @internal CLDR
|
|
* @deprecated This API is ICU internal only.
|
|
*/
|
|
@Deprecated
|
|
public long getShiftedValue() {
|
|
return integerValue * baseFactor + decimalDigits;
|
|
}
|
|
|
|
private void writeObject(
|
|
ObjectOutputStream out)
|
|
throws IOException {
|
|
throw new NotSerializableException();
|
|
}
|
|
|
|
private void readObject(ObjectInputStream in
|
|
) throws IOException, ClassNotFoundException {
|
|
throw new NotSerializableException();
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*
|
|
* @internal CLDR
|
|
* @deprecated This API is ICU internal only.
|
|
*/
|
|
@Deprecated
|
|
@Override
|
|
public boolean isNaN() {
|
|
return Double.isNaN(source);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*
|
|
* @internal CLDR
|
|
* @deprecated This API is ICU internal only.
|
|
*/
|
|
@Deprecated
|
|
@Override
|
|
public boolean isInfinite() {
|
|
return Double.isInfinite(source);
|
|
}
|
|
|
|
// would be convenient to have getExponent, like the other methods
|
|
public int getExponent() {
|
|
return exponent;
|
|
}
|
|
|
|
}
|