| 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; |
| } |
| |
| } |