/*--------------------------------------------------------------------------- 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 provides static library methods for * encoding binary representations of decimal * floating-point numbers using the * decimal32, decimal64 and decimal128 formats. * @author Dave Clark, IBM Ease of Use */ public final class DecimalEncoder { // private constructor to prevent instantiation private DecimalEncoder() { } /** * Return a bit field containing a decimalNNN encoding of * a (quiet) Not-a-Number value. * @param encoding the encoding to be used. * @return a bit field of 32, 64 or 128 bits according to * the decimalNNN format selected. * @see DecimalConstants#DECIMAL32 * @see DecimalConstants#DECIMAL64 * @see DecimalConstants#DECIMAL128 */ public static BitString encodeNaNAsDecimal(final DecimalConstants.DecimalEncoding encoding) { // create a bit-string result of the appropriate length final BitString result = new BitString(encoding.getNumberOfBits()); // we use '11111' for the combination field, and then // another '0' at the exponent high bit to denote quiet NaN result.fillBits(DecimalConstants.BIT_COMBINATION, 5, true); result.resetBit(DecimalConstants.BIT_EXPONENT); // that's all return result; } /** * Return a bit field containing a decimalNNN encoding of * a signaling Not-a-Number value. * @param encoding the encoding to be used. * @return a bit field of 32, 64 or 128 bits according to * the decimalNNN format selected. * @see DecimalConstants#DECIMAL32 * @see DecimalConstants#DECIMAL64 * @see DecimalConstants#DECIMAL128 */ public static BitString encodeSignalingNaNAsDecimal(final DecimalConstants.DecimalEncoding encoding) { // create a bit-string result of the appropriate length final BitString result = new BitString(encoding.getNumberOfBits()); // we use '11111' for the combination field, and then // a '1' at the exponent high bit to denote signaling NaN result.fillBits(DecimalConstants.BIT_COMBINATION, 5, true); result.setBit(DecimalConstants.BIT_EXPONENT); // that's all return result; } /** * Return a bit field containing a decimalNNN encoding of * an infinity value. * @param encoding the encoding to be used. * @param sign +1 or 0 to specify negative or positive infinity. * @return a bit field of 32, 64 or 128 bits according to * the decimalNNN format selected. * @see DecimalConstants#DECIMAL32 * @see DecimalConstants#DECIMAL64 * @see DecimalConstants#DECIMAL128 */ public static BitString encodeInfinityAsDecimal(final DecimalConstants.DecimalEncoding encoding, final int sign) { // create a bit-string result of the appropriate length final BitString result = new BitString(encoding.getNumberOfBits()); // check the sign if ((sign != 0) && (sign != 1)) throw new IllegalArgumentException("Invalid sign value: " + sign); //$NON-NLS-1$ // we store the sign, '11110' for the combination field, and 0s to the end result.storeBit(DecimalConstants.BIT_SIGN, sign > 0); result.fillBits(DecimalConstants.BIT_COMBINATION, 4, true); result.fillBits(DecimalConstants.BIT_COMBINATION + 4, false); // that's all return result; } /** * Return a bit field containing a DecimalNNN encoding of * a finite numeric value. This method produces an exact * encoding, and the coefficient must have exactly the * appropriate length and the exponent must be in the allowed * range. This method will not perform any of the adjustments * which can allow values to be encoded in numerically equivalent * forms that fit the encoding, nor will it perform any rounding * or overflow/underflow adjustments. If the sign, coefficient * and exponent supplied cannot be encoded in the specified * encoding exactly as they are given, then an exception is thrown. * @param encoding the encoding to be used. * @param sign +1 or 0 to specify a negative or positive number. * @param coefficient an array of decimal digit values in the * range 0-9, with a length of 7 (for * {@link DecimalConstants#DECIMAL32 decimal32}), 16 (for * {@link DecimalConstants#DECIMAL64 decimal64}) or 34 (for * {@link DecimalConstants#DECIMAL128 decimal128}), and with no leading * zeroes (except when the coefficient is all zeroes). * @param decimal exponent in the range -101 to 89 (for * {@link DecimalConstants#DECIMAL32 decimal32}), -398 to 368 (for * {@link DecimalConstants#DECIMAL64 decimal64}) or -6176 to 6110 (for * {@link DecimalConstants#DECIMAL128 decimal128}). * @return a bit field of 32, 64 or 128 bits, according to * the decimalNNN format selected, exactly encoding the supplied value. * @throws IllegalArgumentException if the supplied value cannot be * encoded in the specified encoding exactly as supplied. * @see DecimalConstants#DECIMAL32 * @see DecimalConstants#DECIMAL64 * @see DecimalConstants#DECIMAL128 */ public static BitString encodeFiniteAsDecimal(final DecimalConstants.DecimalEncoding encoding, final int sign, final int[] coefficient, final int exponent) { // create a bit-string result of the appropriate length final BitString result = new BitString(encoding.getNumberOfBits()); // check the sign if ((sign != 0) && (sign != 1)) throw new IllegalArgumentException("Invalid sign value: " + sign); //$NON-NLS-1$ // check the exponent if ( (exponent < encoding.getMinimumEncodableExponent()) || (exponent > encoding.getMaximumEncodableExponent()) ) throw new IllegalArgumentException("Exponent value is out of range for encoding: " + exponent); //$NON-NLS-1$ //$NON-NLS-2$ // we store the sign result.storeBit(DecimalConstants.BIT_SIGN, sign > 0); // the top two bits of the exponent will go into the combination field. final int exponent_continuation_bits = encoding.getNumberOfExponentContinuationBits(); final int biased_exponent = exponent - encoding.getMinimumEncodableExponent(); final int exponent_highbits = biased_exponent >> exponent_continuation_bits; // the combination field format depends on the // most significant coefficient digit value if (coefficient[0] >= 8) { // combination field is [1:1:e0:e1:d0-8] result.fillBits(DecimalConstants.BIT_COMBINATION, 2, true); result.storeBits(DecimalConstants.BIT_COMBINATION + 2, 2, exponent_highbits); result.storeBit(DecimalConstants.BIT_COMBINATION + 4, (coefficient[0] > 8)); } else { // combination field is [e0:e1:d00:d01:d02] result.storeBits(DecimalConstants.BIT_COMBINATION, 2, exponent_highbits); result.storeBits(DecimalConstants.BIT_COMBINATION + 2, 3, coefficient[0]); } // the exponent field receives the remaining exponent bits result.storeBits(DecimalConstants.BIT_EXPONENT, exponent_continuation_bits, biased_exponent); // the remaining digits are now written in DPD, // three digits at a time, using ten bits at a time int next_bit = DecimalConstants.BIT_EXPONENT + exponent_continuation_bits; for (int digit = 1; digit < coefficient.length; digit += 3, next_bit += 10) result.storeBits(next_bit, 10, packDPD(coefficient[digit], coefficient[digit+1], coefficient[digit+2])); // that's all return result; } /** * Return a bit field containing a decimalNNN encoding of * a generic floating-point number. This method may need to * adjust the value being encoded, and will set appropriate * flags in the supplied conversion flags to indicate what * situations occurred. * @param encoding the encoding to be used. * @param number a floating-point number to encode. * @param flags a set of conversion flags which may be set * during the generation of the encoded result. * @param extended if true then range extension is enabled, * and otherwise range extension is disabled. Range extension * allows numbers whose magnitude is less than the normal range * lower threshold to be stored without setting the * {@link ArithmeticConditions#isUnderflow underflow} flag provided they * can be exactly accommodated in the encoding. Such numbers will * continue to set the {@link ArithmeticConditions#isSubnormal subnormal} * flag, and if such a number triggers rounding and sets the * {@link ArithmeticConditions#isInexact inexact} flag then the * {@link ArithmeticConditions#isUnderflow underflow} flag will be set * regardless of whether range extension is enabled or not. When * range extension is disabled, whenever the * {@link ArithmeticConditions#isSubnormal subnormal} flag is set the * {@link ArithmeticConditions#isUnderflow underflow} flag will also be set. * @return a bit field of 32, 64 or 128 bits according to * the decimalNNN format selected. * @see DecimalConstants#DECIMAL32 * @see DecimalConstants#DECIMAL64 * @see DecimalConstants#DECIMAL128 */ public static BitString encodeDecimal(final DecimalConstants.DecimalEncoding encoding, final ModelNumber number, final DecimalConstants.RoundingMode rounding, final ArithmeticConditions flags) { if (number == null) throw new IllegalArgumentException("Null number supplied"); //$NON-NLS-1$ if (number.isNaN()) return encodeNaNAsDecimal(encoding); else if (number.isSignalingNaN()) return encodeSignalingNaNAsDecimal(encoding); else if (number.isInfinity()) return encodeInfinityAsDecimal(encoding, number.getSign()); else if (number.isFinite()) return encodeDecimal(encoding, number.getSign(), number.getCoefficient(), number.getExponent(), rounding, flags); else throw new IllegalArgumentException("Unrecognised floating-point number type"); //$NON-NLS-1$ } /** * Return a bit field containing a decimalNNN encoding of * a finite numeric value. This method may need to * adjust the value being encoded, and will set appropriate * flags in the supplied conversion flags to indicate what * situations occurred. * @param encoding the encoding to be used. * @param sign +1 or 0 to specify a negative or positive number. * @param coefficient an array of decimal digits, with no leading * zeroes (except when the coefficient is a single zero). * @param exponent decimal exponent. * @param rounding the rounding mode * @param flags a set of conversion flags which may be set * during the generation of the encoded result. * @return a bit field of 32, 64 or 128 bits according to * the decimalNNN format selected. * @see DecimalConstants#DECIMAL32 * @see DecimalConstants#DECIMAL64 * @see DecimalConstants#DECIMAL128 */ public static BitString encodeDecimal(final DecimalConstants.DecimalEncoding encoding, final int sign, final int[] coefficient, final int exponent, final DecimalConstants.RoundingMode rounding, final ArithmeticConditions flags) { // nulls are definitely out if (coefficient == null) throw new IllegalArgumentException("Null coefficient value supplied"); //$NON-NLS-1$ if (rounding == null) throw new IllegalArgumentException("Null rounding mode supplied"); //$NON-NLS-1$ // we should not have leading zeroes (unless the coefficient is just "0") final int coefficient_length = coefficient.length; final boolean coefficient_zero = (coefficient[0] == 0); if ((coefficient_length > 1) && coefficient_zero) throw new IllegalArgumentException("The coefficient must not have leading zeroes"); //$NON-NLS-1$ // check that each digit character is really a digit // check that the remainder of the coefficient is valid for (int i = 0; i < coefficient.length; i++) if ((coefficient[i] < 0) || (coefficient[i] > 9)) throw new IllegalArgumentException("The coefficient must contain only digits 0-9"); //$NON-NLS-1$ final BitString result; // see how many digits we should be using final int number_of_digits = encoding.getNumberOfCoefficientDigits(); // see how much spare room or length excess the coefficient has final int coefficient_excess = (number_of_digits < coefficient_length) ? ((coefficient_length - number_of_digits) + roundSpillover(coefficient, number_of_digits, sign, rounding)) : 0; final int coefficient_spare = (number_of_digits > coefficient_length) ? (number_of_digits - coefficient_length) : 0; final int coefficient_nonzeroes = coefficient_length - 1 + roundSpillover(coefficient, 0, sign, rounding); int coefficient_zeroes = 0; for (int i = coefficient_length - 1; (coefficient[i] == 0) && (i > 0); i--) coefficient_zeroes++; // see how much spare range or range excess the exponent has final int exponent_max = encoding.getMaximumEncodableExponent(); final int exponent_min = encoding.getMinimumEncodableExponent(); final int exponent_min_normal = exponent_min + number_of_digits - 1; final int exponent_upper_excess = (exponent > exponent_max) ? (exponent - exponent_max) : 0; final int exponent_upper_spare = (exponent < exponent_max) ? (exponent_max - exponent) : 0; final int exponent_lower_excess = (exponent < exponent_min) ? (exponent_min - exponent) : 0; // first see if we're subnormal, and set that flag if (!coefficient_zero && (exponent + coefficient_length - 1 < exponent_min_normal)) { flags.setSubnormal(); } if (coefficient_zero) { // *** first case: coefficient is zero, exponent may be freely adjusted final int clamped_exponent; // if the exponent is out of range, clamp it if (exponent_upper_excess > 0) { clamped_exponent = exponent_max; flags.setClamped(); } else if (exponent_lower_excess > 0) { clamped_exponent = exponent_min; flags.setClamped(); } else clamped_exponent = exponent; // use zeroes as the coefficient, and clamp the exponent result = encodeFiniteAsDecimal(encoding, sign, new int[number_of_digits], clamped_exponent); } else if (exponent_upper_excess + coefficient_excess > exponent_upper_spare + coefficient_spare) { // *** second case: exponent is too high, after adjusting up to allow // for coefficient excess and down by using all // coefficient spare space for adding trailing zeroes flags.setOverflow(); flags.setRounded(); flags.setInexact(); // encode an infinity result = encodeInfinityAsDecimal(encoding, sign); } else if (exponent_upper_excess > 0) { // *** third case: exponent is too high, but as we've eliminated the // second case already we know we can now use some // coefficient spare space for adding trailing zeroes flags.setClamped(); // pad the coefficient, adding trailing zeroes for the exponent excess final int[] digits = adjustCoefficient(coefficient, coefficient_length + exponent_upper_excess, number_of_digits, sign, rounding, flags); // we clamp the exponent into range result = encodeFiniteAsDecimal(encoding, sign, digits, exponent_max); } else if (exponent_lower_excess > coefficient_nonzeroes) { // *** fourth case: exponent is too low, even after adjusting up by // rounding the coefficient to one digit (or even to // zero digits if that causes a one digit spillover) flags.setUnderflow(); // round anyway, because this may round away from zero in some modes final int[] digits = adjustCoefficient(coefficient, coefficient_length - exponent_lower_excess, number_of_digits, sign, rounding, flags); result = encodeFiniteAsDecimal(encoding, sign, digits, exponent_min); } else if (exponent_lower_excess > 0) { // *** fifth case: exponent is too low, but as we've eliminated the // fourth case already we know we can now round the // coefficient for adding leading zeroes // round the coefficient to make it fit and bring the exponent into range final int adjustment = Math.max(coefficient_excess, exponent_lower_excess); final int[] digits = adjustCoefficient(coefficient, coefficient_length - adjustment, number_of_digits, sign, rounding, flags); // adjust the exponent to account for the rounding that has been done result = encodeFiniteAsDecimal(encoding, sign, digits, exponent + adjustment); } else { // *** all remaining cases: should be OK after rounding final int[] digits; if (coefficient_length == number_of_digits) digits = coefficient; else { // we need to pad (leading zeroes) or round the coefficient to make it fit digits = adjustCoefficient(coefficient, coefficient_length - coefficient_excess, number_of_digits, sign, rounding, flags); } // adjust the exponent to account for the rounding that has been done result = encodeFiniteAsDecimal(encoding, sign, digits, exponent + coefficient_excess); } // inexactness for subnormals triggers underflow if (flags.isSubnormal() && flags.isInexact()) flags.setUnderflow(); return result; } // this little function returns 1 if rounding the specified // coefficient to a given number of digits would cause an // extra digit to be required at the leading end, 0 otherwise private static int roundSpillover(final int[] coefficient, final int number_of_digits, final int sign, final DecimalConstants.RoundingMode rounding) { int result = 0; if (coefficient.length > number_of_digits) { // it's only possible to get spillover if all the digits // up to the rounding point are 9s. boolean allnines = true; for (int i = 0; allnines && (i < number_of_digits); i++) allnines = (coefficient[i] == 9); if (allnines) { // we characterise the rounding needed using three flags: // 'previousodd' is true if the last digit is 1,3,5,7 or 9 // 'upper' is true if the next digit is 5-9 inclusive // 'half' is true if the next digit is 0 or 5 and there // are no non-zero digits following it final boolean previousodd = (number_of_digits > 0) ? ((coefficient[number_of_digits - 1] % 2) == 1) : false; final boolean upper = (number_of_digits >= 0) ? (coefficient[number_of_digits] >= 5) : false; boolean half = (number_of_digits < 0) || (coefficient[number_of_digits] == 0) || (coefficient[number_of_digits] == 5); // while we're on track for a 'half', check for non-zero digits for (int i = Math.max(0, number_of_digits + 1); half && (i < coefficient.length); i++) half = (coefficient[i] == 0); // we only round if there's something non-zero to take into account if (upper || !half) { // decide whether we need to increment the last digit result = mustIncrement(upper, half, sign, rounding, previousodd) ? 1 : 0; } } } return result; } /** * Perform rounding and padding of a coefficient. * @param coefficient the coefficient digits to be rounded * @param number_of_digits the number of coefficient digits which are * actually to be considered. This can be more than the number of digits * provided, in which case extra trailing zeroes are assumed, or it can * be fewer than the number of digits provided (to force rounding * independently of the output length) and can go down to zero and even * negative. Certain digit sequences and certain rounding modes may combine * to cause one extra digit to be generated at the leading end ("spillover"). * @param output_length the number of digits of result to be returned. * @param rounding the rounding mode to be applied if necessary. * @param flags conversion flags to be set if certain conditions arise. * The {@link ArithmeticConditions#isRounded() rounded} flag will be set if * the output length is less than the number of coefficient digits to be * considered, or if the number of coefficient digits to be considered * is less than the number of provided coefficient digits. The * {@link ArithmeticConditions#isInexact() inexact} flag will be set if any * of the unused coefficient digits are non-zero, and in this case the * specified rounding mode is also applied. */ private static int[] adjustCoefficient(final int[] coefficient, final int coefficient_digits, final int output_length, final int sign, final DecimalConstants.RoundingMode rounding, final ArithmeticConditions flags) { // prepare the result final int[] result = new int[output_length]; // find out how many coefficient digits we will actually use final int output_digits = Math.min(coefficient_digits, output_length); final int coefficient_length = coefficient.length; final int copy_digits = Math.min(output_digits, coefficient_length); // copy the digits that will fit, right-aligning (padding with leading // zeroes) if there is room left over, and leaving trailing zeroes if // we're copying fewer actual coefficient digits than we're due to output if (copy_digits > 0) System.arraycopy(coefficient, 0, result, output_length - output_digits, copy_digits); // if we used fewer digits than were provided, then we've rounded if (copy_digits < coefficient_length) { flags.setRounded(); // we characterise the rounding needed using two flags: // 'previousodd' is true if the last digit is 1,3,5,7 or 9 // 'upper' is true if the next digit was 5-9 inclusive // 'half' is true if the next digit was 0 or 5 and there // were no non-zero digits following it final boolean previousodd = (copy_digits > 0) ? ((coefficient[copy_digits - 1] % 2) == 1) : false; final boolean upper = (copy_digits >= 0) ? (coefficient[copy_digits] >= 5) : false; boolean half = (copy_digits < 0) || (coefficient[copy_digits] == 0) || (coefficient[copy_digits] == 5); // while we're on track for a 'half', check for non-zero digits for (int i = Math.max(0, copy_digits + 1); half && (i < coefficient_length); i++) half = (coefficient[i] == 0); // we only round if there's something non-zero to take into account if (upper || !half) { flags.setInexact(); // decide whether we need to increment the last digit boolean increment = mustIncrement(upper, half, sign, rounding, previousodd); // apply the necessary incrementing, and assume that there // is room to apply spillover if it occurs for (int increment_digit = output_length - output_digits + copy_digits - 1; increment; increment_digit--) { increment = (result[increment_digit] == 9); result[increment_digit] = increment ? 0 : (result[increment_digit] + 1); } } } return result; } // this method encapsulates the supported rounding rules, // by reporting whether the last retained digit needs to // be incremented or not. private static boolean mustIncrement(final boolean upper, final boolean half, final int sign, final DecimalConstants.RoundingMode rounding, boolean previousodd) { boolean result = false; if (rounding == DecimalConstants.ROUNDING_FLOOR) // in negative direction: positives stay, negatives increment result = (sign == 1); else if (rounding == DecimalConstants.ROUNDING_CEILING) // in positive direction: negatives stay, positives increment result = (sign == 0); else if (rounding == DecimalConstants.ROUNDING_DOWN) // in toward zero direction: all stay result = false; else if (rounding == DecimalConstants.ROUNDING_UP) // in away from zero direction: all increment result = true; else if (rounding == DecimalConstants.ROUNDING_HALF_DOWN) // nearest, halfs towards zero: only more-than-halfs increment result = upper && !half; else if (rounding == DecimalConstants.ROUNDING_HALF_UP) // nearest, halfs away from zero: only halfs-and-more increment result = upper; else if (rounding == DecimalConstants.ROUNDING_HALF_EVEN) // nearest, halfs increment after odds result = upper && (!half || previousodd); return result; } /** * Pack three decimal digits (0-9) into 10 bits using * Densely-Packed Decimal (DPD) format. * @param digit1 the first of the three digits to be packed. * @param digit2 the second of the three digits to be packed. * @param digit3 the third of the three digits to be packed. * @return the result of packing the three digits using DPD * format, with the first bit in bit 9 and the last bit in * bit 0 of the returned int. */ private static int packDPD(final int digit1, final int digit2, final int digit3) { final int result; // the DPD format is based on the combination of digits // with a value of 8 or 9 (ie, requiring a fourth bit) final int combination = ((digit1 & 8) >> 1) | ((digit2 & 8) >> 2) | ((digit3 & 8) >> 3); switch (combination) { case 0: // no, no, no result = (digit1 << 7) | (digit2 << 4) | digit3; break; case 1: // no, no, yes result = (digit1 << 7) | (digit2 << 4) | (digit3 & 1) | 8; break; case 2: // no, yes, no result = (digit1 << 7) | ((digit3 & 6) << 4) | ((digit2 & 1) << 4) | (digit3 & 1) | 10; break; case 3: // no, yes, yes result = (digit1 << 7) | ((digit2 & 1) << 4) | (digit3 & 1) | 78; break; case 4: // yes, no, no result = ((digit3 & 6) << 7) | ((digit1 & 1) << 7) | (digit2 << 4) | (digit3 & 1) | 12; break; case 5: // yes, no, yes result = ((digit2 & 6) << 7) | ((digit1 & 1) << 7) | ((digit2 & 1) << 4) | (digit3 & 1) | 46; break; case 6: // yes, yes, no result = ((digit3 & 6) << 7) | ((digit1 & 1) << 7) | ((digit2 & 1) << 4) | (digit3 & 1) | 14; break; case 7: // yes, yes, yes result = ((digit1 & 1) << 7) | ((digit2 & 1) << 4) | (digit3 & 1) | 110; break; default: throw new RuntimeException("The laws of arithmetic have failed. Prepare to re-boot universe."); //$NON-NLS-1$ } return result; } /** * Return a ModelNumber corresponding to the decimal * string representation supplied. * @param number a string representation of a decimal or special value. * @return a ModelNumber representing the supplied value. */ public static ModelNumber toModelNumber(final String value) { final ModelNumber result; // we can't parse an empty string if (value.length() == 0) reject(value); // capture the sign, -1 for neg, +1 for pos, 0 for no sign final char signch = value.charAt(0); final int sign = (signch == '-') ? -1 : ((signch == '+') ? 1 : 0); final String residue = (sign != 0) ? value.substring(1).toUpperCase() : value.toUpperCase(); if ("NAN".equals(residue)) //$NON-NLS-1$ { // NaN -- no sign permitted if (sign != 0) reject(value); result = ModelNumber.NAN; } else if ("SNAN".equals(residue)) //$NON-NLS-1$ { // signaling NaN -- no sign permitted if (sign != 0) reject(value); result = ModelNumber.SIGNALING_NAN; } else if ("INFINITY".equals(residue)) //$NON-NLS-1$ { // Infinity, optionally signed, default is positive result = (sign < 0) ? ModelNumber.NEGATIVE_INFINITY : ModelNumber.POSITIVE_INFINITY; } else { // Finite, optionally signed, default is positive result = parseFiniteDecimal((sign < 0) ? 1 : 0, residue, value); } return result; } // complete the parsing of a finite string representation // sign = +1/0, residue is uppercase and has no sign private static ModelNumber parseFiniteDecimal(final int sign, final String residue, final String number) { String coefficient = residue; int exponent = 0; // if we have an exponent part, parse it final int epos = residue.indexOf('E'); if (epos >= 0) { coefficient = residue.substring(0, epos); // capture the sign, -1 for neg, +1 for pos, 0 for no sign final char exponent_signch = (epos + 1 < residue.length()) ? residue.charAt(epos + 1) : ' '; final int exponent_sign = (exponent_signch == '-') ? -1 : ((exponent_signch == '+') ? 1 : 0); final int exponent_magnitude = parseExponent((exponent_sign != 0) ? residue.substring(epos + 2) : residue.substring(epos + 1), number); exponent = (exponent_sign < 0) ? (-exponent_magnitude) : exponent_magnitude; } // if there's a point in the coefficient, expunge it final int dpos = coefficient.indexOf('.'); if (dpos >= 0) { coefficient = coefficient.substring(0, dpos) + coefficient.substring(dpos+1); exponent -= coefficient.length() - dpos; } if (coefficient.length() == 0) reject(number); return ModelNumber.createFinite(sign, 10, coefficient, exponent); } // parse a decimal exponent value, not including sign private static int parseExponent(final String exponent, final String number) { // if our result exceeds this threshold and we need to shift // one further digit, we've blown our limit final int threshold = (Integer.MAX_VALUE - 9) / 10; int result = 0; for (int i = 0; i < exponent.length(); i++) { if (result > threshold) throw new IllegalArgumentException("Exponent is too large for representation by ModelNumber: " + number); //$NON-NLS-1$ final char digitch = exponent.charAt(i); final int digit = Character.digit(digitch, 10); if (digit < 0) reject(number); result = (result * 10) + digit; } return result; } private static void reject(final String number) { throw new IllegalArgumentException("Invalid string representation: '" + number + '\''); //$NON-NLS-1$ } }