/*--------------------------------------------------------------------------- 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 * interpreting and decoding binary representations * of decimal floating-point numbers using the * decimal32, decimal64 and decimal128 formats. * @author Dave Clark, IBM Ease of Use */ public final class DecimalDecoder { // private constructor to prevent instantiation private DecimalDecoder() { } /** * Return a floating-point number obtained by parsing * binary encodings of decimal floating-point numbers * in the decimal32, decimal64 and decimal128 * formats. * @param bits a bit field containing a DecimalNNN encoded value. * @return a floating-point number. */ public static ModelNumber decodeDecimal(final BitString bits) { final ModelNumber result; // the combination field determines the encoding final int combination = bits.getBits(DecimalConstants.BIT_COMBINATION, 5); if (combination == 31) // 11111 { // either a signalling or quiet NaN result = bits.getBit(DecimalConstants.BIT_EXPONENT) ? ModelNumber.SIGNALING_NAN : ModelNumber.NAN; } else if (combination == 30) // 11110 { // an infinity, positive or negative result = bits.getBit(DecimalConstants.BIT_SIGN) ? ModelNumber.NEGATIVE_INFINITY : ModelNumber.POSITIVE_INFINITY; } else if (combination >= 24) { // a finite number with an 8 or 9 as leading coefficient digit final int leading_digit = 8 + (combination & 1); final int exponent_highbits = (combination & 6) >> 1; result = decodeFinite(bits, leading_digit, exponent_highbits); } else { // a finite number with an 8 or 9 as leading coefficient digit final int leading_digit = combination & 7; final int exponent_highbits = (combination & 24) >> 3; result = decodeFinite(bits, leading_digit, exponent_highbits); } return result; } /** * Return the encoding which uses the specified number of bits. This * method always returns an encoding, or throws an exception if no * DecimalNNN encoding uses that number of bits. * @return the decimal encoding using the specified number of bits. * @throws IllegalArgumentException if there is no matching encoding. */ public static DecimalConstants.DecimalEncoding getEncodingForNumberOfBits(final int numberOfBits) { final DecimalConstants.DecimalEncoding result = (numberOfBits == DecimalConstants.DECIMAL32.getNumberOfBits()) ? DecimalConstants.DECIMAL32 : ((numberOfBits == DecimalConstants.DECIMAL64.getNumberOfBits()) ? DecimalConstants.DECIMAL64 : ((numberOfBits == DecimalConstants.DECIMAL128.getNumberOfBits()) ? DecimalConstants.DECIMAL128 : null)); if (result == null) throw new IllegalArgumentException("Invalid DecimalNNN format (" + numberOfBits + " bits)"); //$NON-NLS-1$ //$NON-NLS-2$ return result; } /** * Extract the exponent continuation and coefficient continuation * from a binary DecimalNNN encoding, and return the floating-point * number encoded. */ private static ModelNumber decodeFinite(final BitString bits, final int leading_digit, final int exponent_highbits) { // final out which encoding we have final int nbits = bits.getNumberOfBits(); final DecimalConstants.DecimalEncoding encoding = getEncodingForNumberOfBits(nbits); // extract the exponent continuation and reattach the high bits final int exponent_continuation_bits = encoding.getNumberOfExponentContinuationBits(); final int biased_exponent = (exponent_highbits << exponent_continuation_bits) | bits.getBits(DecimalConstants.BIT_EXPONENT, exponent_continuation_bits); final int exponent = biased_exponent + encoding.getMinimumEncodableExponent(); // collect the coefficient string starting with the leading digit final int[] coefficient = new int[encoding.getNumberOfCoefficientDigits()]; coefficient[0] = leading_digit; // now decode the coefficient digits written in DPD, // three digits at a time, consuming ten bits at a time int next_bit = DecimalConstants.BIT_EXPONENT + exponent_continuation_bits; for (int digit = 1; next_bit < nbits; digit += 3, next_bit += 10) { unpackDPD(bits.getBits(next_bit, 10), coefficient, digit); } // construct a floating-point number using the sign, a // radix of 10, the collected coefficient string, and // the de-biased exponent value return ModelNumber.createFinite(bits.getBit(DecimalConstants.BIT_SIGN) ? 1 : 0, 10, coefficient, exponent); } /** * Unpack 10 bits in Densely-Packed Decimal (DPD) format into * three decimal digits (0-9), writing the three digit values * into the specified array at the supplied position. * @param bits three decimal digits packed using DPD format, with * the first bit in bit 9 and the last bit in bit 0 of an int. * @param result an array into which three integer digit values * will be written. * @param index the index into the array where the first integer * digit value will be written. The second and third values will * be written into the two successive positions. */ private static void unpackDPD(final int bits, final int[] result, final int index) { // the DPD format is characterised by bit 6, possibly bits 7 and 8, // and possibly further bits 3 and 4 (counting from 0 as the most // significant to 9 as the least significant bit). we build a // three-bit composite to distinguish the possible cases. final int combination = ((bits & 14) == 14) // when bits 6,7,8 are all 1, this part enumerates the combinations // of bits 3,4, taking the values 1-0-0, 1-0-1, 1-1-0, 1-1-1 respectively // when bits 3,4 are 0-0, 0-1, 1-0, 1-1 respectively. ? (((bits & 96) >> 5) | 4) // otherwise, this part enumerates the combinations of bits 6,7,8, taking // the value 0-0-0 if bit 6 is 0, and taking the values 0-1-1, 0-1-0 and // 0-0-1 respectively when bit 6 is 1 and bits 7,8 are 0-0, 0-1, 1-0 respectively : (((bits & 8) == 8) ? (((~bits) & 6) >> 1) : 0); // we will first decode the three digits as three Binary-Coded Decimal // 4-bit nybbles in 12 bits in an int, with the first digit high final int decoded; switch (combination) { case 0: // bit 6 is 0 decoded = ((bits & 896) << 1) | (bits & 119); break; case 1: // bits 6,7,8 are 1-1-0 decoded = ((bits & 128) << 1) | (bits & 113) | ((bits & 768) >> 7) | 2048; break; case 2: // bits 6,7,8 are 1-0-1 decoded = ((bits & 896) << 1) | (bits & 17) | ((bits & 96) >> 4) | 128; break; case 3: // bits 6,7,8 are 1-0-0 decoded = ((bits & 896) << 1) | (bits & 113) | 8; break; case 4: // bits 6,7,8 are 1-1-1, bits 3,4 are 0-0 decoded = ((bits & 128) << 1) | (bits & 17) | ((bits & 768) >> 7) | 2176; break; case 5: // bits 6,7,8 are 1-1-1, bits 3,4 are 0-1 decoded = ((bits & 128) << 1) | (bits & 17) | ((bits & 768) >> 3) | 2056; break; case 6: // bits 6,7,8 are 1-1-1, bits 3,4 are 1-0 decoded = ((bits & 896) << 1) | (bits & 17) | 136; break; case 7: // bits 6,7,8 are 1-1-1, bits 3,4 are 1-1 // NB: we ignore values of bits 0,1 in this case decoded = ((bits & 128) << 1) | (bits & 17) | 2184; break; default: throw new RuntimeException("The laws of arithmetic have failed. Prepare to re-boot universe."); //$NON-NLS-1$ } result[index] = (decoded & 3840) >> 8; result[index + 1] = (decoded & 240) >> 4; result[index + 2] = (decoded & 15); } /** * Return a string representation of the specified * value in decimal 'Scientific' format. * @throws IllegalArgumentException if the value is finite * and its coefficient is not expressed with a radix of 10. */ public static String toDecimalScientificString(final ModelNumber value) { final String result; if (value.isNaN()) result = "NaN"; //$NON-NLS-1$ else if (value.isSignalingNaN()) result = "sNaN"; //$NON-NLS-1$ else if (value.isInfinity()) result = (value.getSign() > 0) ? "-Infinity" : "Infinity"; //$NON-NLS-1$ //$NON-NLS-2$ else { if (value.getRadix() != 10) throw new IllegalStateException("Value does not have a radix of 10"); //$NON-NLS-1$ final StringBuffer sresult = new StringBuffer(); if (value.getSign() > 0) sresult.append('-'); // compute the display exponent for scientific ("x.yyyyEnnn") format final String coeff_string = value.getCoefficientAsString(); final int coeff_length = coeff_string.length(); final int exponent = value.getExponent(); final int adjusted_exponent = exponent + coeff_length - 1; if ((exponent > 0) || (adjusted_exponent < -6)) { // use scientific format sresult.append(coeff_string.charAt(0)); if (coeff_length > 1) sresult.append('.').append(coeff_string.substring(1)); sresult.append('E'); if (adjusted_exponent >= 0) sresult.append('+'); sresult.append(adjusted_exponent); } else { // display as plain decimal without exponent, padding with leading zeroes if necessary final int digits = Math.max(coeff_length, 1 - exponent); for (int i = coeff_length - digits; i < coeff_length; i++) { if (i == coeff_length + exponent) sresult.append('.'); if (i < 0) sresult.append('0'); else sresult.append(coeff_string.charAt(i)); } } result = sresult.toString(); } return result; } /** * Return a string representation of the specified * value in decimal 'Engineering' format. * @throws IllegalArgumentException if the value is finite * and its coefficient is not expressed with a radix of 10. */ public static String toDecimalEngineeringString(final ModelNumber value) { return toDecimalScientificString(value); } }