/*--------------------------------------------------------------------------- Decimal encoding sample code (C) Copyright International Business Machines Corporation 2003 All Rights Reserved. This sample code is intended to illustrate encoding of decimal floating-point numbers in binary bit strings, and corresponding decoding. Please see the document "General Decimal Arithmetic" at http://speleotrove.com/decimal for a full description of the encoding formats used. This sample code is experimental, and may contain errors. It is offered on an as-is basis. In particular, this sample code is designed to be illustrative rather than to be an implementation optimised for any particular purpose. Indeed, a number of obvious optimisations are omitted in the interests of maximising clarity. Please send comments, suggestions, and corrections to the author: Dave Clark IBM UK, PO Box 31, Birmingham Road, Warwick CV34 5JL, UK dave_clark@uk.ibm.com --------------------------------------------------------------------------*/ package com.ibm.eou.decimal; /** * This class represents a generic floating-point number, * with almost no limitation on precision or magnitude, and also * includes some special values. Finite numbers are represented * as a sign, a finite-length coefficient expressed as a string * of digits in a given radix, and a signed integer exponent. * The coefficient length is limited only by storage, and the * exponent is limited only to values which can be stored by a * Java 'int' type. Infinities can also have a sign, and two * not-a-number (NaN) values (quiet and signaling) are also * supported. * @author Dave Clark, IBM Ease of Use */ public class ModelNumber extends java.lang.Number implements java.lang.Comparable { // the digit values we recognise private final static String DIGITS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; //$NON-NLS-1$ /** * Constant specifying a quiet NaN. */ private final static int TYPE_QUIET_NAN = 0; /** * Constant specifying a signaling NaN. */ private final static int TYPE_SIGNALING_NAN = 1; /** * Constant specifying an infinity. */ private final static int TYPE_INFINITY = 2; /** * Constant specifying a finite number. */ private final static int TYPE_FINITE = 3; private final int fType; private final int fSign; private final int fRadix; private final int[] fCoefficient; private final int fExponent; /** * A ModelNumber representing a (quiet) NaN. * @see #SIGNALING_NAN */ public final static ModelNumber NAN = new ModelNumber(TYPE_QUIET_NAN, 0, 0, null, 0); /** * A ModelNumber representing a signaling NaN. * @see #NAN */ public final static ModelNumber SIGNALING_NAN = new ModelNumber(TYPE_SIGNALING_NAN, 0, 0, null, 0); /** * A ModelNumber representing a positive infinity. */ public final static ModelNumber POSITIVE_INFINITY = new ModelNumber(TYPE_INFINITY, 0, 0, null, 0); /** * A ModelNumber representing a negative infinity. */ public final static ModelNumber NEGATIVE_INFINITY = new ModelNumber(TYPE_INFINITY, 1, 0, null, 0); /** * Return a ModelNumber representing a finite number. * The number is specified as a sign (0=positive, 1=negative), * a radix, a coefficient comprising digit values in the range * zero to radix-1, and a signed integer exponent. * @param sign the sign (0=positive, 1=negative). * @param radix the radix in which the coefficient is expressed. * @param coefficient the coefficient. * @param exponent the signed integer exponent. * @return a ModelNumber representing the finite number. */ public static ModelNumber createFinite(final int sign, final int radix, final int[] coefficient, final int exponent) { // check that the sign is valid if ((sign != 0) && (sign != 1)) throw new IllegalArgumentException("The sign must be 0 or 1"); //$NON-NLS-1$ // check the the radix is supported if ((radix < 2) || (radix > 36)) throw new IllegalArgumentException("The radix must be in the range 2 to 36"); //$NON-NLS-1$ // nulls are definitely out if (coefficient == null) throw new IllegalArgumentException("Null coefficient value"); //$NON-NLS-1$ // scan for the first non-zero coefficient digit, if any, // or the final digit (the string remains a single '0') int firstnonzero = 0; while ((firstnonzero < coefficient.length - 1) && (coefficient[firstnonzero] == 0)) firstnonzero++; // strip any leading zeroes final int[] stripped_coefficient; if (firstnonzero > 0) { stripped_coefficient = new int[coefficient.length - firstnonzero]; System.arraycopy(coefficient, firstnonzero, stripped_coefficient, 0, stripped_coefficient.length); } else stripped_coefficient = coefficient; // check that the remainder of the coefficient is valid for (int i = 0; i < stripped_coefficient.length; i++) if ((stripped_coefficient[i] < 0) || (stripped_coefficient[i] > radix - 1)) throw new IllegalArgumentException("The coefficient must contain only digits from zero to radix-1"); //$NON-NLS-1$ return new ModelNumber(TYPE_FINITE, sign, radix, stripped_coefficient, exponent); } /** * Return a ModelNumber representing a finite number. * The number is specified as a sign (0=positive, 1=negative), * a radix, a coefficient comprising digit characters * '0'-'9','A'-'Z' (as required) and a signed integer exponent. * @param sign the sign (0=positive, 1=negative). * @param radix the radix in which the coefficient is expressed. * @param coefficient the coefficient. * @param exponent the signed integer exponent. * @return a ModelNumber representing the finite number. */ public static ModelNumber createFinite(final int sign, final int radix, final String coefficient, final int exponent) { // nulls are definitely out if (coefficient == null) throw new IllegalArgumentException("Null coefficient value"); //$NON-NLS-1$ // check the the radix is supported if ((radix < 2) || (radix > 36)) throw new IllegalArgumentException("The radix must be in the range 2 to 36"); //$NON-NLS-1$ // convert the string to an int array of digit values final int coefficient_digits[] = new int[coefficient.length()]; for (int i = 0; i < coefficient_digits.length; i++) coefficient_digits[i] = parseDigit(coefficient.charAt(i)); // delegate the remaining function to the other signature return createFinite(sign, radix, coefficient_digits, exponent); } public static int parseDigit(final char digit) { return DIGITS.indexOf(Character.toUpperCase(digit)); } private ModelNumber(final int type, final int sign, final int radix, final int[] coefficient, final int exponent) { fType = type; fSign = sign; fRadix = radix; fCoefficient = coefficient; fExponent = exponent; } /** * Return true if the value is a quiet NaN. */ public boolean isNaN() { return (fType == TYPE_QUIET_NAN); } /** * Return true if the value is a signaling NaN. */ public boolean isSignalingNaN() { return (fType == TYPE_SIGNALING_NAN); } /** * Return true if the value is an infinity. Infinities * also have a sign. * @see #getSign() */ public boolean isInfinity() { return (fType == TYPE_INFINITY); } /** * Return true if the value is finite. Finite numbers also * have a sign, a radix, a coefficient, and an exponent. * @see #getSign() * @see #getRadix() * @see #getCoefficient() * @see #getExponent() */ public boolean isFinite() { return (fType == TYPE_FINITE); } /** * Return true if the value is a zero. */ public boolean isZero() { // because we keep our coefficient with all leading // zeroes (except the final digit) stripped, this is // a very easy test return (fCoefficient[0] == 0); } /** * Return the sign of the value. A sign of 0 is * positive and 1 is negative. */ public int getSign() { if ((fType != TYPE_INFINITY) && (fType != TYPE_FINITE)) throw new IllegalStateException("Value does not have a sign"); //$NON-NLS-1$ return fSign; } /** * Return the radix in which the coefficient is expressed. */ public int getRadix() { if (fType != TYPE_FINITE) throw new IllegalStateException("Value does not have a radix"); //$NON-NLS-1$ return fRadix; } /** * Return the coefficient digit values. */ public int[] getCoefficient() { if (fType != TYPE_FINITE) throw new IllegalStateException("Value does not have a coefficient"); //$NON-NLS-1$ return (int[])fCoefficient.clone(); } /** * Return the coefficient as a string of digit characters. */ public String getCoefficientAsString() { if (fType != TYPE_FINITE) throw new IllegalStateException("Value does not have a coefficient"); //$NON-NLS-1$ final StringBuffer result = new StringBuffer(fCoefficient.length); for (int i = 0; i < fCoefficient.length; i++) result.append(DIGITS.charAt(fCoefficient[i])); return result.toString(); } /** * Return the exponent. */ public int getExponent() { if (fType != TYPE_FINITE) throw new IllegalStateException("Value does not have an exponent"); //$NON-NLS-1$ return fExponent; } /** * @see java.lang.Number#byteValue() */ public byte byteValue() { return (byte)doubleValue(); } /** * @see java.lang.Number#doubleValue() */ public double doubleValue() { final double result; if ((fType == TYPE_QUIET_NAN) || (fType == TYPE_SIGNALING_NAN)) result = Double.NaN; else if (fType == TYPE_INFINITY) result = (fSign > 0) ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY; else { double before_point = 0; final int before_point_digits = fCoefficient.length + fExponent; for (int i = 0; i < before_point_digits - 1; i++) { if (i < fCoefficient.length) before_point += fCoefficient[i]; before_point *= fRadix; } if (before_point_digits <= fCoefficient.length) before_point += fCoefficient[before_point_digits-1]; double after_point = 0; for (int i = before_point_digits; i < fCoefficient.length; i++) { after_point += fCoefficient[i]; after_point /= fRadix; } final double combined = before_point + after_point; result = (fSign > 0) ? (-combined) : combined; } return result; } /** * @see java.lang.Number#floatValue() */ public float floatValue() { return (float)doubleValue(); } /** * @see java.lang.Number#intValue() */ public int intValue() { return (int)doubleValue(); } /** * @see java.lang.Number#longValue() */ public long longValue() { return (long)doubleValue(); } /** * @see java.lang.Number#shortValue() */ public short shortValue() { return (short)doubleValue(); } /** * Return a string representation of the internal * fields of this object. */ public String toString() { final String result; if (fType == TYPE_QUIET_NAN) result = "[NaN]"; //$NON-NLS-1$ else if (fType == TYPE_SIGNALING_NAN) result = "[sNaN]"; //$NON-NLS-1$ else if (fType == TYPE_INFINITY) result = "[" + fSign + ",Inf]"; //$NON-NLS-1$ //$NON-NLS-2$ else result = "[" + fSign + ',' + getCoefficientAsString() +'(' + fRadix + ")," + fExponent + ']'; //$NON-NLS-1$ //$NON-NLS-2$ return result; } /** * Return true if the supplied argument is a ModelNumber * equal to this ModelNumber. Note that only exact equality * is detected: different representations of numerically * equivalent values will compare as different. */ public boolean equals(final Object obj) { if (obj instanceof ModelNumber) { final ModelNumber other = (ModelNumber)obj; return (this.fType == other.fType) && (((this.fType != TYPE_INFINITY) && (this.fType != TYPE_FINITE)) || (this.fSign == other.fSign)) && ((this.fType != TYPE_FINITE) || (this.fCoefficient.equals(other.fCoefficient) && (this.fExponent == other.fExponent))); } else return super.equals(obj); } /** * Return a hash code generated so that two ModelNumbers * which compare equal with {@link #equals equals} will * always produce the same hash code, and ModelNumbers which * do not compare equal with {@link #equals equals} tend * to (but will not necessarily) produce different hash codes. */ public int hashCode() { int result = (37 * fType) + 1; if ((fType == TYPE_INFINITY) || (fType == TYPE_FINITE)) result = (result * (fSign + 1)) + 1; if (fType == TYPE_FINITE) { result = (result * fRadix) + 1; for (int i = 0; i < fCoefficient.length; i++) result = (result * fCoefficient[i]) + 1; result *= fExponent; } return result; } /** * @see java.lang.Comparable#compareTo(Object) */ public int compareTo(Object o) { // this implementation is poor, as it will only successfully // compare values that can be accommodated by the double type. // a fuller implementation is to be added final double me = doubleValue(); final double other = ((ModelNumber)o).doubleValue(); return (me < other) ? -1 : ((me > other) ? 1 : 0); } }