Java tutorial
/* Copyright 2014 (c) Suneido Software Corp. All rights reserved. * Licensed under GPLv2. */ package suneido.util; import java.math.RoundingMode; import com.google.common.base.Strings; /** * Decimal floating point number implementation * using a 64 bit long (treated as unsigned) for the coefficient. * <p> * Value is sign * coef * 10^exp, zeroed value is 0. * <p> * Math "sticks" at infinite when it overflows. * <p> * There is no NaN, inf / inf = 1, 0 / ... = 0, inf / ... = inf */ @Immutable public class Dnum extends Number implements Comparable<Dnum> { private static final long serialVersionUID = 1L; private final long coef; private final byte sign; private final byte exp; final static byte POS = +1; final static byte ZERO = 0; final static byte NEG = -1; public final static byte POS_INF = +2; public final static byte NEG_INF = -2; private static final int EXP_MIN = Byte.MIN_VALUE; private static final int EXP_MAX = Byte.MAX_VALUE; private final static long COEF_MAX = 9_999_999_999_999_999L; public final static int MAX_DIGITS = 16; private final static int MAX_SHIFT = MAX_DIGITS - 1; public final static Dnum Zero = new Dnum(ZERO, 0, 0); public final static Dnum One = new Dnum(POS, 1_000_000_000_000_000L, 1); public final static Dnum MinusOne = new Dnum(NEG, 1_000_000_000_000_000L, 1); public final static Dnum Inf = new Dnum(POS_INF, 1, 0); public final static Dnum MinusInf = new Dnum(NEG_INF, 1, 0); public final static long pow10[] = { 1L, 10L, 100L, 1000L, 10000L, 100000L, 1000000L, 10000000L, 100000000L, 1000000000L, 10000000000L, 100000000000L, 1000000000000L, 10000000000000L, 100000000000000L, 1000000000000000L, 10000000000000000L, 100000000000000000L, 1000000000000000000L }; private final static long halfpow10[] = { // for rounding 0L, 5L, 50L, 500L, 5000L, 50000L, 500000L, 5000000L, 50000000L, 500000000L, 5000000000L, 50000000000L, 500000000000L, 5000000000000L, 50000000000000L, 500000000000000L, 5000000000000000L, 50000000000000000L, 500000000000000000L, 5000000000000000000L }; /** raw - no normalization */ private Dnum(byte sign, long coef, int exp) { this.sign = sign; assert coef >= 0; this.coef = coef; assert EXP_MIN <= exp && exp <= EXP_MAX; this.exp = (byte) exp; } public static Dnum from(long n) { if (n == 0) return Zero; byte sign = POS; if (n < 0) { n = -n; sign = NEG; } int p = maxShift(n); n *= pow10[p]; return new Dnum(sign, n, MAX_DIGITS - p); } public static Dnum from(double d) { // needed for math/trig functions if (Double.isNaN(d)) throw new NumberFormatException("can't convert NaN to number"); if (Double.isInfinite(d)) return d < 0 ? MinusInf : Inf; // probably not the most efficient, but simple return parse(Double.toString(d)); } static int ilog10(long x) { // based on Hacker's Delight if (x == 0) return 0; int y = (19 * (63 - Long.numberOfLeadingZeros(x))) >> 6; if (y < 18 && x >= pow10[y + 1]) ++y; return y; } // the maximum we can safely shift left (*10) public static int maxShift(long x) { int i = ilog10(x); return i > MAX_SHIFT ? 0 : MAX_SHIFT - i; } /** normalizes */ public static Dnum from(int sign, long coef, int exp) { if (sign == 0 || coef == 0 || exp < EXP_MIN) { return Zero; } else if (sign == POS_INF) { return Inf; } else if (sign == NEG_INF) { return MinusInf; } else { boolean atmax = false; while (coef > COEF_MAX) { coef = (coef + 5) / 10; // drop/round least significant digit ++exp; atmax = true; } if (!atmax) { int p = maxShift(coef); coef *= pow10[p]; exp -= p; } if (exp > EXP_MAX) return inf(sign); return new Dnum(sign < 0 ? NEG : POS, coef, exp); } } private static class Parser { final String s; int i = 0; int exp = 0; Parser(String s) { this.s = s; } Dnum parse() { byte sign = POS; if (match('-')) sign = NEG; else match('+'); if (s.startsWith("inf", i)) return inf(sign); long coef = getCoef(); exp += getExp(); if (i < s.length()) // didn't consume entire string throw new RuntimeException("invalid number"); if (coef == 0 || exp < EXP_MIN) return Zero; else if (exp > EXP_MAX) return inf(sign); else return Dnum.from(sign, coef, exp); } private long getCoef() { boolean digits = false; boolean before_decimal = true; // skip leading zeroes, no effect on result while (match('0')) digits = true; if (next() == '.' && i + 1 < s.length()) digits = false; long n = 0; int p = MAX_SHIFT; while (true) { if (isdigit(next())) { digits = true; // ignore extra decimal places // required for double conversion if (next() != '0' && p >= 0) n += (next() - '0') * pow10[p]; --p; ++i; } else if (before_decimal) { exp = MAX_SHIFT - p; if (!match('.')) break; before_decimal = false; if (!digits) { for (; match('0'); --exp) digits = true; } } else break; } if (!digits) throw new RuntimeException("numbers require at least one digit"); return n; } int getExp() { int e = 0; if (match('e') || match('E')) { int esign = match('-') ? -1 : 1; match('+'); for (; isdigit(next()); ++i) e = e * 10 + (next() - '0'); e *= esign; } return e; } private char next() { return i < s.length() ? s.charAt(i) : 0; } private boolean match(char c) { if (next() == c) { i++; return true; } return false; } private static boolean isdigit(char c) { return '0' <= c && c <= '9'; } } public static Dnum parse(String s) { return new Parser(s).parse(); } /** * Default conversion to string. * Should match cSuneido. */ @Override public String toString() { final int MAX_LEADING_ZEROS = 7; if (isZero()) return "0"; StringBuilder sb = new StringBuilder(20); if (sign < 0) sb.append('-'); if (isInf()) return sb.append("inf").toString(); char digits[] = new char[MAX_DIGITS]; int nd = digits(digits); int e = exp - nd; if (-MAX_LEADING_ZEROS <= exp && exp <= 0) { // decimal to the left sb.append('.').append(Strings.repeat("0", -e - nd)).append(digits, 0, nd); } else if (-nd < e && e <= -1) { // decimal within int d = nd + e; sb.append(digits, 0, d); if (nd > 1) sb.append('.').append(digits, d, nd - d); } else if (0 < exp && exp <= MAX_DIGITS) { // decimal to the right sb.append(digits, 0, nd).append(Strings.repeat("0", e)); } else { // use scientific notation sb.append(digits, 0, 1); if (nd > 1) sb.append('.').append(digits, 1, nd - 1); sb.append('e').append(exp - 1); } return sb.toString(); } /** * Puts the decimal digits of coef into the array, * without trailing zeroes. * digits should be MAX_DIGITS in length. * @return The number of digits placed in the array (0 to MAX_DIGITS). */ public int digits(char[] digits) { int i = MAX_SHIFT; int nd = 0; for (long c = coef; c != 0; --i, ++nd) { digits[nd] = (char) ('0' + (char) (c / pow10[i])); c %= pow10[i]; } return nd; } /** for debug/test */ String show() { StringBuilder sb = new StringBuilder(); switch (sign) { case NEG_INF: return "--"; case NEG: sb.append("-"); break; case 0: sb.append("z"); break; case POS: sb.append("+"); break; case POS_INF: return "++"; default: sb.append("?"); break; } if (coef == 0) sb.append('0'); else { sb.append("."); long c = coef; for (int i = MAX_SHIFT; i >= 0 && c != 0; --i) { long p = pow10[i]; int digit = (int) (c / p); c %= p; sb.append((char) ('0' + digit)); } } sb.append('e').append(exp); return sb.toString(); } public boolean isZero() { return sign == ZERO; } /** @return true if plus or minus infinite */ public boolean isInf() { return sign == NEG_INF || sign == POS_INF; } public Dnum negate() { return new Dnum((byte) -sign, coef, exp); } public Dnum abs() { return sign < 0 ? new Dnum((byte) -sign, coef, exp) : this; } // @return -1, 0, +1 for negative, zero, positive public int signum() { return sign < 0 ? -1 : sign > 0 ? +1 : 0; } // @return the value of the sign byte, including NEG_INF (-2) and POS_INF (+2) public int sign() { return sign; } public int exp() { return exp; } public long coef() { return coef; } // add and subtract -------------------------------------------------------- public static Dnum add(Dnum x, Dnum y) { if (x.sign == 0) return y; if (y.sign == 0) return x; if (x.sign == POS_INF || x.sign == NEG_INF) return (y.sign == -x.sign) ? Zero : x; if (y.sign == POS_INF || y.sign == NEG_INF) return y; return (x.sign == y.sign) ? uadd(x, y) : usub(x, y); } public static Dnum sub(Dnum x, Dnum y) { if (x.sign == 0) return y.negate(); if (y.sign == 0) return x; if (x.sign == POS_INF || x.sign == NEG_INF) return (y.sign == x.sign) ? Zero : x; if (y.sign == POS_INF) return MinusInf; if (y.sign == NEG_INF) return Inf; return (x.sign == y.sign) ? usub(x, y) : uadd(x, y); } /** unsigned add */ private static Dnum uadd(Dnum x, Dnum y) { if (x.exp > y.exp) { Align y2 = new Align(y); if (!align(x, y2)) return x; else return from(x.sign, x.coef + y2.coef, x.exp); } else if (x.exp < y.exp) { Align x2 = new Align(x); if (!align(y, x2)) return y; else return from(x.sign, x2.coef + y.coef, x2.exp); } else return from(x.sign, x.coef + y.coef, x.exp); } /** unsigned subtract */ private static Dnum usub(Dnum x, Dnum y) { if (x.exp > y.exp) { Align y2 = new Align(y); return align(x, y2) ? from(x.sign, x.coef - y2.coef, x.exp) : x; } else if (x.exp < y.exp) { Align x2 = new Align(x); return align(y, x2) ? from(-x.sign, y.coef - x2.coef, x2.exp) : y.negate(); } else return x.coef > y.coef ? from(x.sign, x.coef - y.coef, x.exp) : from(-x.sign, y.coef - x.coef, x.exp); } /** modifies y */ private static boolean align(Dnum x, Align y) { int yshift = ilog10(y.coef); int e = x.exp - y.exp; if (e > yshift) return false; yshift = e; y.coef = (y.coef + halfpow10[yshift]) / pow10[yshift]; y.exp += yshift; return true; } /** mutable */ private static class Align { long coef; byte exp; Align(Dnum dn) { this.coef = dn.coef; this.exp = dn.exp; } } // multiply ---------------------------------------------------------------- private static final long E7 = 10_000_000L; public static Dnum mul(Dnum x, Dnum y) { int sign = (x.sign * y.sign); if (sign == 0) return Zero; else if (x.isInf() || y.isInf()) return inf(sign); int e = x.exp + y.exp; // split unevenly to use full 64 bit range to get more precision // and avoid needing xlo * ylo long xhi = x.coef / E7; // 9 digits long xlo = x.coef % E7; // 7 digits long yhi = y.coef / E7; // 9 digits long ylo = y.coef % E7; // 7 digits long c = xhi * yhi + (xlo * yhi + ylo * xhi) / E7; return from(sign, c, e - 2); } // divide ------------------------------------------------------------------ public static Dnum div(Dnum x, Dnum y) { int sign = x.sign * y.sign; if (sign == 0) return x.isZero() ? Zero : /* y.isZero() */ inf(x.sign); if (x.isInf()) return y.isInf() ? sign < 0 ? MinusOne : One : inf(sign); if (y.isInf()) return Zero; long q = Div128.divide(x.coef, y.coef); return from(sign, q, x.exp - y.exp); } // ------------------------------------------------------------------------- private static Dnum inf(int sign) { return sign < 0 ? MinusInf : sign > 0 ? Inf : Zero; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (int) (coef ^ (coef >>> 32)); result = prime * result + exp; result = prime * result + sign; return result; } /** Handles Integer */ @Override public boolean equals(Object other) { if (this == other) return true; if (other == null) return false; Dnum that; if (other instanceof Integer) that = Dnum.from((int) other); else if (getClass() != other.getClass()) return false; else that = (Dnum) other; return sign == that.sign && exp == that.exp && coef == that.coef; } @Override public int compareTo(Dnum that) { return cmp(this, that); } public static int cmp(Dnum x, Dnum y) { if (x.sign > y.sign) return +1; else if (x.sign < y.sign) return -1; int sign = x.sign; if (sign == 0 || sign == NEG_INF || sign == POS_INF) return 0; if (x.exp < y.exp) return -sign; if (x.exp > y.exp) return +sign; return sign * Long.compare(x.coef, y.coef); } /** @return true if it's an integer, but may not fit in int or long */ public boolean integral() { if (sign == 0) return true; if (sign == NEG_INF || sign == POS_INF || exp <= 0) // e.g. .1 return false; if (exp >= MAX_DIGITS) // e.g. 1e99 return true; return (coef % pow10[MAX_DIGITS - exp]) == 0; } public Dnum integer() { return integer(RoundingMode.DOWN); } /** @return The integer part of the value i.e. truncating the fractional. * But won't necessarily fit in int or long */ public Dnum integer(RoundingMode mode) { if (sign == 0 || sign == NEG_INF || sign == POS_INF || exp >= MAX_DIGITS) return this; if (exp <= 0) { if (mode == RoundingMode.UP || (mode == RoundingMode.HALF_UP && exp == 0 && coef >= One.coef * 5)) return new Dnum(sign, One.coef, exp + 1); return Zero; } int e = MAX_DIGITS - exp; long frac = coef % pow10[e]; if (frac == 0) return this; long i = coef - frac; if ((mode == RoundingMode.UP && frac > 0) || (mode == RoundingMode.HALF_UP && frac >= halfpow10[e])) return Dnum.from(sign, i + pow10[e], exp); // normalize return new Dnum(sign, i, exp); } /** @return The integer part of the value i.e. truncating the fractional. * But won't necessarily fit in int or long */ public Dnum frac() { if (sign == 0 || sign == NEG_INF || sign == POS_INF || exp >= MAX_DIGITS) return Zero; if (exp <= 0) return this; long frac = coef % pow10[MAX_DIGITS - exp]; return frac == coef ? this : Dnum.from(sign, frac, exp); } /** Throws if value doesn't fit in int */ @Override public int intValue() { long n = longValue(); if (Integer.MIN_VALUE <= n && n <= Integer.MAX_VALUE) return (int) n; throw new RuntimeException("can't convert number to integer"); } /** Throws if value doesn't fit in long */ @Override public long longValue() { if (sign == ZERO) return 0; if (sign != NEG_INF && sign != POS_INF) { if (0 < exp && exp < MAX_DIGITS && (coef % pow10[MAX_DIGITS - exp]) == 0) return sign * (coef / pow10[MAX_DIGITS - exp]); // usual case if (exp == MAX_DIGITS) return sign * coef; if (exp == MAX_DIGITS + 1) return sign * (coef * 10); if (exp == MAX_DIGITS + 2) return sign * (coef * 100); if (exp == MAX_DIGITS + 3 && coef < Long.MAX_VALUE / 1000) return sign * (coef * 1000); } throw new RuntimeException("can't convert number to integer"); } /** returns Long.MIN_VALUE if not representable as long */ public static long longOrMin(byte sign, long coef, byte exp) { if (sign == ZERO) return 0; if (sign != NEG_INF && sign != POS_INF) { if (0 < exp && exp < MAX_DIGITS && (coef % pow10[MAX_DIGITS - exp]) == 0) return sign * (coef / pow10[MAX_DIGITS - exp]); // usual case if (exp == MAX_DIGITS) return sign * coef; if (exp == MAX_DIGITS + 1) return sign * (coef * 10); if (exp == MAX_DIGITS + 2) return sign * (coef * 100); if (exp == MAX_DIGITS + 3 && coef < Long.MAX_VALUE / 1000) return sign * (coef * 1000); } return Long.MIN_VALUE; } /** @return integer value if in range, else Integer.MIN_VALUE * This is a workaround for Java not being able to (efficiently) * return multiple values. * The drawback is that you can't handle the full int range. */ public int intOrMin() { return intOrMin(sign, coef, exp); } public static int intOrMin(byte sign, long coef, byte exp) { if (sign == ZERO) return 0; if (sign != NEG_INF && sign != POS_INF && 0 < exp && exp <= 10 && (coef % pow10[MAX_DIGITS - exp]) == 0) { long n = sign * (coef / pow10[MAX_DIGITS - exp]); if (Integer.MIN_VALUE < n && n <= Integer.MAX_VALUE) return (int) n; } return Integer.MIN_VALUE; } /** @return Integer (boxed) value if in range, else null * Used by SuContainer.canonical which boxes anyway * and can't use intOrMin because we want the full range. */ public Object intObject() { if (sign == ZERO) return 0; if (sign != NEG_INF && sign != POS_INF && 0 < exp && exp <= 10 && (coef % pow10[MAX_DIGITS - exp]) == 0) { long n = sign * (coef / pow10[MAX_DIGITS - exp]); if (Integer.MIN_VALUE <= n && n <= Integer.MAX_VALUE) return (int) n; } return null; } @Override public float floatValue() { throw new UnsupportedOperationException(); } @Override public double doubleValue() { // needed for math/trig methods switch (sign) { case ZERO: return 0.0; case NEG_INF: return Double.NEGATIVE_INFINITY; case POS_INF: return Double.POSITIVE_INFINITY; } int e = exp - MAX_DIGITS; return sign * (e < 0 ? coef / dpow10[-e] : coef * dpow10[e]); } private static final double dpow10[] = { 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22, 1e23, 1e24, 1e25, 1e26, 1e27, 1e28, 1e29, 1e30, 1e31, 1e32, 1e33, 1e34, 1e35, 1e36, 1e37, 1e38, 1e39, 1e40, 1e41, 1e42, 1e43, 1e44, 1e45, 1e46, 1e47, 1e48, 1e49, 1e50, 1e51, 1e52, 1e53, 1e54, 1e55, 1e56, 1e57, 1e58, 1e59, 1e60, 1e61, 1e62, 1e63, 1e64, 1e65, 1e66, 1e67, 1e68, 1e69, 1e70, 1e71, 1e72, 1e73, 1e74, 1e75, 1e76, 1e77, 1e78, 1e79, 1e80, 1e81, 1e82, 1e83, 1e84, 1e85, 1e86, 1e87, 1e88, 1e89, 1e90, 1e91, 1e92, 1e93, 1e94, 1e95, 1e96, 1e97, 1e98, 1e99, 1e100, 1e101, 1e102, 1e103, 1e104, 1e105, 1e106, 1e107, 1e108, 1e109, 1e110, 1e111, 1e112, 1e113, 1e114, 1e115, 1e116, 1e117, 1e118, 1e119, 1e120, 1e121, 1e122, 1e123, 1e124, 1e125, 1e126, 1e127, 1e128, 1e129, 1e130, 1e131, 1e132, 1e133, 1e134, 1e135, 1e136, 1e137, 1e138, 1e139, 1e140, 1e141, 1e142, 1e143, 1e144 }; /** * r >= 0 means the specified number of decimal places. * r < 0 means abs(r) number of 0's to the left of the decimal place. * @return A new Dnum. */ public Dnum round(int r, RoundingMode mode) { if (isZero() || isInf() || r >= MAX_DIGITS) return this; if (r <= -MAX_DIGITS) return Zero; Dnum n = new Dnum(sign, coef, exp + r); // multiply by 10^r n = n.integer(mode); if (n.sign == POS || n.sign == NEG) // i.e. not zero or inf return new Dnum(n.sign, n.coef, n.exp - r); return n; } public Dnum round(int r) { return round(r, RoundingMode.HALF_UP); } }