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