All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nom.tam.util.ByteFormatter Maven / Gradle / Ivy

package nom.tam.util;

/*
 * #%L
 * nom.tam FITS library
 * %%
 * Copyright (C) 2004 - 2024 nom-tam-fits
 * %%
 * This is free and unencumbered software released into the public domain.
 *
 * Anyone is free to copy, modify, publish, use, compile, sell, or
 * distribute this software, either in source code form or as a compiled
 * binary, for any purpose, commercial or non-commercial, and by any
 * means.
 *
 * In jurisdictions that recognize copyright laws, the author or authors
 * of this software dedicate any and all copyright interest in the
 * software to the public domain. We make this dedication for the benefit
 * of the public at large and to the detriment of our heirs and
 * successors. We intend this dedication to be an overt act of
 * relinquishment in perpetuity of all present and future rights to this
 * software under copyright law.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 * #L%
 */

/**
 * This class provides mechanisms for efficiently formatting numbers and Strings. Data is appended to existing byte
 * arrays. Note that the formatting of real or double values may differ slightly (in the last bit) from the standard
 * Java packages since this routines are optimized for speed rather than accuracy.
 * 

* The methods in this class create no objects. *

* If a number cannot fit into the requested space the truncateOnOverlow flag controls whether the formatter will * attempt to append it using the available length in the output (a la C or Perl style formats). If this flag is set, or * if the number cannot fit into space left in the buffer it is 'truncated' and the requested space is filled with a * truncation fill character. A TruncationException may be thrown if the truncationThrow flag is set. *

* This class does not explicitly support separate methods for formatting reals in exponential notation. Real numbers * near one are by default formatted in decimal notation while numbers with large (or very negative) exponents are * formatted in exponential notation. By setting the limits at which these transitions take place the user can force * either exponential or decimal notation. * * @deprecated This class should not be exposed in the public API and is intended for internal use only in ASCII tables. * Also, it may have overlapping functionality with other classes, which should probably be eliminated * for simplicity's sake (and thus less chance of nasty bugs). * * @see ByteParser */ @Deprecated public final class ByteFormatter { /** String representation of NaN values */ public static final String NOT_A_NUMBER = "NaN"; /** String representation of (positive) infinity values */ public static final String INFINITY = "Infinity"; /** String representation of (negative) infinity values */ public static final String NEGATIVE_INFINITY = "-Infinity"; /** * Maximum magnitude to print in non-scientific notation. */ private static final double DEFAULT_SIMPLE_MAX = 1.e6; /** * Minimum magnitude to print in non-scientific notation. */ private static final double DEFAULT_SIMPLE_MIN = 1.e-3; /** * Digits. We could handle other bases by extending or truncating this list and changing the division by 10 (and * it's factors) at various locations. */ private static final byte[] DIGITS = {(byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9'}; private static final long DOUBLE_EXPONENT_BIT_MASK = 0x7FF0000000000000L; private static final int DOUBLE_EXPONENT_EXCESS = 52; /** * The hidden bit in normalized double numbers. */ private static final long DOUBLE_EXPONENT_NORMALIZE_BIT = 0x0010000000000000L; /** * Used to check if double is normalized */ private static final int DOUBLE_MIN_EXPONENT = -1023; private static final int DOUBLE_SHIFT_BASE = 17; private static final int DOUBLE_SHIFT_LIMIT = 200; private static final long DOUBLE_VALUE_BIT_MASK = 0x000FFFFFFFFFFFFFL; private static final int FLOAT_EXPONENT_BIT_MASK = 0x7F800000; private static final int FLOAT_EXPONENT_EXCESS = 23; /** * The hidden bit in normalized floating point numbers. */ private static final int FLOAT_EXPONENT_NORMALIZE_BIT = 0x00800000; /** * Used to check if float is normalized */ private static final int FLOAT_MIN_EXPONENT = -127; private static final int FLOAT_SHIFT_BASE = 8; private static final int FLOAT_SHIFT_LIMIT = 30; private static final int FLOAT_VALUE_BIT_MASK = 0x007FFFFF; private static final double I_LOG_10 = 1. / Math.log(ByteFormatter.NUMBER_BASE); private static final long LONG_TO_INT_MODULO = 1000000000L; private static final int MAX_LONG_LENGTH = 19; /** * The maximum single digit integer. Special case handling when rounding up and when incrementing the exponent. */ private static final int MAXIMUM_SINGLE_DIGIT_INTEGER = 9; /** * The maximum two digit integer. When we increment an exponent of this value, the exponent gets longer. Don't need * to worry about 999 since exponents can't be that big. */ private static final int MAXIMUM_TWO_DIGIT_INTEGER = 99; private static final int TEMP_BUFFER_SIZE = 32; /** * The underlying number base used in this class. */ private static final int NUMBER_BASE = 10; /** * Powers of 10. We over extend on both sides. These should perhaps be tabulated rather than computed though it may * be faster to calculate them than to read in the extra bytes in the class file. */ private static final double[] NUMBER_BASE_POWERS; /** * What do we use to fill when we cannot print the number?Default is often used in Fortran. */ private static final byte TRUNCATION_FILL = (byte) '*'; /** * What index of tenpow is 10^0 */ private static final int ZERO_POW; static { // Static initializer int min = (int) Math.floor((int) (Math.log(Double.MIN_VALUE) * ByteFormatter.I_LOG_10)); int max = (int) Math.floor((int) (Math.log(Double.MAX_VALUE) * ByteFormatter.I_LOG_10)); max++; NUMBER_BASE_POWERS = new double[max - min + 1]; for (int i = 0; i < ByteFormatter.NUMBER_BASE_POWERS.length; i++) { ByteFormatter.NUMBER_BASE_POWERS[i] = Math.pow(ByteFormatter.NUMBER_BASE, i + min); } ZERO_POW = -min; } /** * Internal buffers used in formatting fields */ private final byte[] tbuf1 = new byte[ByteFormatter.TEMP_BUFFER_SIZE]; private final byte[] tbuf2 = new byte[ByteFormatter.TEMP_BUFFER_SIZE]; /** * This method formats a double given a decimal mantissa and exponent information. * * @param val The original number * @param buf Output buffer * @param off Offset into buffer * @param len Maximum number of characters to use in buffer. * @param mant A decimal mantissa for the number. * @param lmant The number of characters in the mantissa * @param shift The exponent of the power of 10 that we shifted val to get the given mantissa. * * @return Offset of next available character in buffer. */ private int combineReal(double val, byte[] buf, int off, int len, byte[] mant, int lmant, int shift) { // First get the minimum size for the number double pos = Math.abs(val); boolean simple = false; int minSize; int maxSize; if (pos >= ByteFormatter.DEFAULT_SIMPLE_MIN && pos <= ByteFormatter.DEFAULT_SIMPLE_MAX) { simple = true; } int exp = lmant - shift - 1; int lexp = 0; if (!simple) { lexp = format(exp, tbuf2, 0, ByteFormatter.TEMP_BUFFER_SIZE); minSize = lexp + 2; // e.g., 2e-12 maxSize = lexp + lmant + 2; // add in "." and e } else if (exp >= 0) { minSize = exp + 1; // e.g. 32 // Special case. E.g., 99.9 has // minumum size of 3. int i; for (i = 0; i < lmant && i <= exp; i++) { if (mant[i] != (byte) '9') { break; } } if (i > exp && i < lmant && mant[i] >= (byte) '5') { minSize++; } maxSize = lmant + 1; // Add in "." if (maxSize <= minSize) { // Very large numbers. maxSize = minSize + 1; } } else { minSize = 2; maxSize = 1 + Math.abs(exp) + lmant; } if (val < 0) { minSize++; } // Can the number fit? if (minSize > len || minSize > buf.length - off) { truncationFiller(buf, off, len); return off + len; } // Now begin filling in the buffer. if (val < 0) { buf[off] = (byte) '-'; off++; len--; } if (simple) { return Math.abs(mantissa(mant, lmant, exp, simple, buf, off, len)); } off = mantissa(mant, lmant, 0, simple, buf, off, len - lexp - 1); if (off < 0) { off = -off; len -= off; // Handle the expanded exponent by filling if (exp == MAXIMUM_SINGLE_DIGIT_INTEGER || exp == MAXIMUM_TWO_DIGIT_INTEGER) { // Cannot fit... if (off + len == minSize) { truncationFiller(buf, off, len); return off + len; } // Steal a character from the mantissa. off--; } exp++; lexp = format(exp, tbuf2, 0, ByteFormatter.TEMP_BUFFER_SIZE); } buf[off] = (byte) 'E'; off++; System.arraycopy(tbuf2, 0, buf, off, lexp); return off + lexp; } /** * Format a boolean into an existing array. * * @param val value to write * @param array the array to fill * * @return Offset of next available character in buffer. */ public int format(boolean val, byte[] array) { return format(val, array, 0, array.length); } /** * Format a boolean into an existing array * * @param val The boolean to be formatted * @param array The buffer in which to format the data. * @param off The starting offset within the buffer. * @param len The maximum number of characters to use use in formatting the number. * * @return Offset of next available character in buffer. */ public int format(boolean val, byte[] array, int off, int len) { if (len > 0) { if (val) { array[off] = (byte) 'T'; } else { array[off] = (byte) 'F'; } off++; } return off; } /** * Format a double into an array. * * @param val The double to be formatted. * @param array The array in which to place the result. * * @return The number of characters used. @ * if the value was truncated */ public int format(double val, byte[] array) { return format(val, array, 0, array.length); } /** * Format a double into an existing character array. *

* This is hard to do exactly right... The JDK code does stuff with rational arithmetic and so forth. We use a much * simpler algorithm which may give an answer off in the lowest order bit. Since this is pure Java, it should still * be consistent from machine to machine. *

* Recall that the binary representation of the double is of the form d = 0.bbbbbbbb x 2n * where there are up to 53 binary digits in the binary fraction (including the assumed leading 1 bit for normalized * numbers). We find a value m such that 10m d is between 253 and * 263. This product will be exactly convertible to a long with no loss of precision. * Getting the decimal representation for that is trivial (see formatLong). This is a decimal mantissa and we have * an exponent (-m). All we have to do is manipulate the decimal point to where we want to see it. * Errors can arise due to roundoff in the scaling multiplication, but should be no more than a single bit. * * @param val Double to be formatted * @param buf Buffer in which result is to be stored * @param off Offset within buffer * @param len Maximum length of integer * * @return offset of next unused character in input buffer. */ public int format(double val, byte[] buf, int off, int len) { double pos = Math.abs(val); // Special cases -- It is OK if these get truncated. if (pos == 0.) { return format("0.0", buf, off, len); } if (Double.isNaN(val)) { return format(NOT_A_NUMBER, buf, off, len); } if (Double.isInfinite(val)) { if (val > 0) { return format(INFINITY, buf, off, len); } return format(NEGATIVE_INFINITY, buf, off, len); } int power = (int) (Math.log(pos) * ByteFormatter.I_LOG_10); int shift = DOUBLE_SHIFT_BASE - power; double scale; double scale2 = 1; // Scale the number so that we get a number ~ n x 10^17. if (shift < DOUBLE_SHIFT_LIMIT) { scale = ByteFormatter.NUMBER_BASE_POWERS[shift + ByteFormatter.ZERO_POW]; } else { // Can get overflow if the original number is // very small, so we break out the shift // into two multipliers. scale2 = ByteFormatter.NUMBER_BASE_POWERS[DOUBLE_SHIFT_LIMIT + ByteFormatter.ZERO_POW]; scale = ByteFormatter.NUMBER_BASE_POWERS[shift - DOUBLE_SHIFT_LIMIT + ByteFormatter.ZERO_POW]; } pos = pos * scale * scale2; // Parse the double bits. long bits = Double.doubleToLongBits(pos); // The exponent should be a little more than 52. int exp = (int) (((bits & DOUBLE_EXPONENT_BIT_MASK) >> DOUBLE_EXPONENT_EXCESS) + DOUBLE_MIN_EXPONENT); long numb = bits & DOUBLE_VALUE_BIT_MASK; // this can never be false because the min exponent of a double is -1022 // but lets keep it if we need it for big decimals if (exp > DOUBLE_MIN_EXPONENT) { // Normalized.... numb |= DOUBLE_EXPONENT_NORMALIZE_BIT; } else { // Denormalized exp++; } // Multiple this number by the excess of the exponent // over 52. This completes the conversion of double to long. numb = numb << exp - DOUBLE_EXPONENT_EXCESS; // Get a decimal mantissa. int ndig = format(numb, tbuf1, 0, ByteFormatter.TEMP_BUFFER_SIZE); // Now format the double. return combineReal(val, buf, off, len, tbuf1, ndig, shift); } /** * Format a float into an array. * * @param val The float to be formatted. * @param array The array in which to place the result. * * @return The number of characters used. @ * if the value was truncated */ public int format(float val, byte[] array) { return format(val, array, 0, array.length); } /** * Format a float into an existing byteacter array. *

* This is hard to do exactly right... The JDK code does stuff with rational arithmetic and so forth. We use a much * simpler algorithm which may give an answer off in the lowest order bit. Since this is pure Java, it should still * be consistent from machine to machine. *

* Recall that the binary representation of the float is of the form d = 0.bbbbbbbb x 2n * where there are up to 24 binary digits in the binary fraction (including the assumed leading 1 bit for normalized * numbers). We find a value m such that 10m d is between 224 and * 232. This product will be exactly convertible to an int with no loss of precision. * Getting the decimal representation for that is trivial (see formatInteger). This is a decimal mantissa and we * have an exponent ( -m). All we have to do is manipulate the decimal point to where we want to see * it. Errors can arise due to roundoff in the scaling multiplication, but should be very small. * * @param val Float to be formatted * @param buf Buffer in which result is to be stored * @param off Offset within buffer * @param len Maximum length of field * * @return Offset of next character in buffer. @ * if the value was truncated */ public int format(float val, byte[] buf, int off, int len) { float pos = Math.abs(val); // Special cases if (pos == 0.) { return format("0.0", buf, off, len); } if (Float.isNaN(val)) { return format(NOT_A_NUMBER, buf, off, len); } if (Float.isInfinite(val)) { if (val > 0) { return format("Infinity", buf, off, len); } return format("-Infinity", buf, off, len); } int power = (int) Math.floor(Math.log(pos) * ByteFormatter.I_LOG_10); int shift = FLOAT_SHIFT_BASE - power; float scale; float scale2 = 1; // Scale the number so that we get a number ~ n x 10^8. if (shift < FLOAT_SHIFT_LIMIT) { scale = (float) ByteFormatter.NUMBER_BASE_POWERS[shift + ByteFormatter.ZERO_POW]; } else { // Can get overflow if the original number is // very small, so we break out the shift // into two multipliers. scale2 = (float) ByteFormatter.NUMBER_BASE_POWERS[FLOAT_SHIFT_LIMIT + ByteFormatter.ZERO_POW]; scale = (float) ByteFormatter.NUMBER_BASE_POWERS[shift - FLOAT_SHIFT_LIMIT + ByteFormatter.ZERO_POW]; } pos = pos * scale * scale2; // Parse the float bits. int bits = Float.floatToIntBits(pos); // The exponent should be a little more than 23 int exp = ((bits & FLOAT_EXPONENT_BIT_MASK) >> FLOAT_EXPONENT_EXCESS) + FLOAT_MIN_EXPONENT; int numb = bits & FLOAT_VALUE_BIT_MASK; if (exp > FLOAT_MIN_EXPONENT) { // Normalized.... numb |= FLOAT_EXPONENT_NORMALIZE_BIT; } else { // Denormalized exp++; } // Multiple this number by the excess of the exponent // over 24. This completes the conversion of float to int // (<<= did not work on Alpha TruUnix) numb = numb << exp - FLOAT_EXPONENT_EXCESS; // Get a decimal mantissa. int ndig = format(numb, tbuf1, 0, ByteFormatter.TEMP_BUFFER_SIZE); // Now format the float. return combineReal(val, buf, off, len, tbuf1, ndig, shift); } /** * Format an int into an array. * * @param val The int to be formatted. * @param array The array in which to place the result. * * @return The number of characters used. @ * if the value was truncated */ public int format(int val, byte[] array) { return format(val, array, 0, array.length); } /** * Format an int into an existing array. * * @param val Integer to be formatted * @param buf Buffer in which result is to be stored * @param off Offset within buffer * @param len Maximum length of integer * * @return offset of next unused character in input buffer. */ public int format(int val, byte[] buf, int off, int len) { // Special case if (val == Integer.MIN_VALUE) { if (len > ByteFormatter.NUMBER_BASE) { return format("-2147483648", buf, off, len); } truncationFiller(buf, off, len); return off + len; } int pos = Math.abs(val); // First count the number of characters in the result. // Otherwise we need to use an intermediary buffer. int ndig = 1; int dmax = ByteFormatter.NUMBER_BASE; while (ndig < ByteFormatter.NUMBER_BASE && pos >= dmax) { ndig++; dmax *= ByteFormatter.NUMBER_BASE; } if (val < 0) { ndig++; } // Truncate if necessary. if (ndig > len || ndig > buf.length - off) { truncationFiller(buf, off, len); return off + len; } // Now insert the actual characters we want -- backwards // We use a do{} while() to handle the caByteFormatterse of 0. off += ndig; int xoff = off - 1; do { buf[xoff] = ByteFormatter.DIGITS[pos % ByteFormatter.NUMBER_BASE]; xoff--; pos /= ByteFormatter.NUMBER_BASE; } while (pos > 0); if (val < 0) { buf[xoff] = (byte) '-'; } return off; } /** * Format a long into an array. * * @param val The long to be formatted. * @param array The array in which to place the result. * * @return The number of characters used. @ * if the value was truncated */ public int format(long val, byte[] array) { return format(val, array, 0, array.length); } /** * Format a long into an existing array. * * @param val Long to be formatted * @param buf Buffer in which result is to be stored * @param off Offset within buffer * @param len Maximum length of integer * * @return offset of next unused character in input buffer. @ * if the value was truncated */ public int format(long val, byte[] buf, int off, int len) { // Special case if (val == Long.MIN_VALUE) { if (len > MAX_LONG_LENGTH) { return format("-9223372036854775808", buf, off, len); } truncationFiller(buf, off, len); return off + len; } long pos = Math.abs(val); // First count the number of characters in the result. // Otherwise we need to use an intermediary buffer. int ndig = 1; long dmax = ByteFormatter.NUMBER_BASE; // Might be faster to try to do this partially in ints while (ndig < MAX_LONG_LENGTH && pos >= dmax) { ndig++; dmax *= ByteFormatter.NUMBER_BASE; } if (val < 0) { ndig++; } // Truncate if necessary. if (ndig > len || ndig > buf.length - off) { truncationFiller(buf, off, len); return off + len; } // Now insert the actual characters we want -- backwards. off += ndig; int xoff = off - 1; buf[xoff] = (byte) '0'; boolean last = pos == 0; while (!last) { // Work on ints rather than longs. int giga = (int) (pos % LONG_TO_INT_MODULO); pos /= LONG_TO_INT_MODULO; last = pos == 0; for (int i = 0; i < MAXIMUM_SINGLE_DIGIT_INTEGER; i++) { buf[xoff] = ByteFormatter.DIGITS[giga % ByteFormatter.NUMBER_BASE]; xoff--; giga /= ByteFormatter.NUMBER_BASE; if (last && giga == 0) { break; } } } if (val < 0) { buf[xoff] = (byte) '-'; } return off; } /** * Insert a string at the beginning of an array. * @return Offset of next available character in buffer. * * @param val The string to be inserted. A null string will insert len spaces. * @param array The buffer in which to insert the string. * * @return Offset of next available character in buffer. */ public int format(String val, byte[] array) { return format(val, array, 0, array.length); } /** * Insert a String into an existing character array. If the String is longer than len, then only the the initial len * characters will be inserted. * * @param val The string to be inserted. A null string will insert len spaces. * @param array The buffer in which to insert the string. * @param off The starting offset to insert the string. * @param len The maximum number of characters to insert. * * @return Offset of next available character in buffer. */ public int format(String val, byte[] array, int off, int len) { if (val == null) { for (int i = 0; i < len; i++) { array[off + i] = (byte) ' '; } return off + len; } int slen = val.length(); if (slen > len || slen > array.length - off) { val = val.substring(0, len); slen = len; } System.arraycopy(AsciiFuncs.getBytes(val), 0, array, off, slen); return off + slen; } /** * Write the mantissa of the number. This method addresses the subtleties involved in rounding numbers. */ private int mantissa(byte[] mant, int lmant, int exp, boolean simple, byte[] buf, int off, int len) { // Save in case we need to extend the number. int off0 = off; int pos = 0; if (exp < 0) { buf[off] = (byte) '0'; len--; off++; if (len > 0) { buf[off] = (byte) '.'; off++; len--; } // Leading 0s in small numbers. int cexp = exp; while (cexp < -1 && len > 0) { buf[off] = (byte) '0'; cexp++; off++; len--; } } else { // Print out all digits to the left of the decimal. while (exp >= 0 && pos < lmant) { buf[off] = mant[pos]; off++; pos++; len--; exp--; } // Trust we have enough space for this. for (int i = 0; i <= exp; i++) { buf[off] = (byte) '0'; off++; len--; } // Add in a decimal if we have space. if (len > 0) { buf[off] = (byte) '.'; len--; off++; } } // Now handle the digits to the right of the decimal. while (len > 0 && pos < lmant) { buf[off] = mant[pos]; off++; exp--; len--; pos++; } // Now handle rounding. if (pos < lmant && mant[pos] >= (byte) '5') { int i; // Increment to the left until we find a non-9 for (i = off - 1; i >= off0; i--) { if (buf[i] == (byte) '.' || buf[i] == (byte) '-') { continue; } if (buf[i] != (byte) '9') { buf[i]++; break; } buf[i] = (byte) '0'; } // Now we handle 99.99 case. This can cause problems // in two cases. If we are not using scientific notation // then we may want to convert 99.9 to 100., i.e., // we need to move the decimal point. If there is no // decimal point, then we must not be truncating on overflow // but we should be allowed to write it to the // next character (i.e., we are not at the end of buf). // // If we are printing in scientific notation, then we want // to convert 9.99 to 1.00, i.e. we do not move the decimal. // However we need to signal that the exponent should be // incremented by one. // // We cannot have aligned the number, since that requires // the full precision number to fit within the requested // length, and we would have printed out the entire // mantissa (i.e., pos >= lmant) if (i < off0) { buf[off0] = (byte) '1'; boolean foundDecimal = false; for (i = off0 + 1; i < off; i++) { if (buf[i] == (byte) '.') { foundDecimal = true; if (simple) { buf[i] = (byte) '0'; i++; if (i < off) { buf[i] = (byte) '.'; } } break; } } if (simple && !foundDecimal) { buf[off + 1] = (byte) '0'; // 99 went to 100 off++; } off = -off; // Signal to change exponent if necessary. } } return off; } /** * Fill the buffer with truncation characters. After filling the buffer, a TruncationException will be thrown if the * appropriate flag is set. */ private void truncationFiller(byte[] buffer, int offset, int length) { for (int i = offset; i < offset + length; i++) { buffer[i] = ByteFormatter.TRUNCATION_FILL; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy