Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
package nom.tam.util;
/*
* #%L
* nom.tam FITS library
* %%
* Copyright (C) 1996 - 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%
*/
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
import nom.tam.fits.FitsFactory;
import nom.tam.fits.HeaderCard;
import nom.tam.fits.LongValueException;
/**
* Formatting number values for use in FITS headers.
*
* @author Attila Kovacs
* @since 1.16
*/
public class FlexFormat {
/**
* Constant to specify the precision (number of decimal places shown) should
* be the natural precision of the number type, or reduced at most to
* {@link #DOUBLE_DECIMALS} as necessary to fit in the alotted space.
*/
public static final int AUTO_PRECISION = -1;
/**
* The maximum number of decimal places to show (after the leading figure)
* for double-precision (64-bit) values.
*/
public static final int DOUBLE_DECIMALS = 16;
/**
* The maximum number of decimal places to show (after the leading figure)
* for single-precision (32-bit) values.
*/
public static final int FLOAT_DECIMALS = 7;
/**
* The minimum number of decimal places to show (after the leading figure)
* for big-decimal values. 64-bit longs are in the +-1E19 range, so they
* provide 18 decimals after the leading figure. We want big integer to
* provideat least as many decimal places as a long, when in exponential
* form...
*/
public static final int MIN_BIGINT_EFORM_DECIMALS = 18;
/**
* The exclusive upper limit floating point value that can be shown in fixed
* format. Values larger or equals to it will always be shown in exponential
* format. This is juist for human readability. If there are more than 5
* figures in front of the decimal place, they become harder to comprehend
* at first sight than the explicit powers of 10 of the exponential format.
*/
private static final double MAX_FIXED = 1e6;
/**
* The smallest floating point value that can be shown in fixed format.
* Values smallert than this value will always be printed in exponential
* format. This is juist for human readability. If there are more than 2
* leading zeroes in front of the decimal place, they become harder to
* comprehend at first sight than the explicit powers of 10 of the
* exponential format.
*/
private static final double MIN_FIXED = 0.001;
/**
* The maximum number of decimal places to show after the leading figure
* (i.e. fractional digits in exponential format). If the value has more
* precision than this value it will be rounded to the specified decimal
* place. The special value {@link #AUTO_PRECISION} can be used to display
* as many of the available decimal places as can fit into the space that is
* available (see {@link #setWidth(int)}.
*/
private int decimals = AUTO_PRECISION;
/**
* The maximum number of characters available for showing number values.
* This class will always return numbers that fit in that space, or else
* throw an exception.
*/
private int width = HeaderCard.FITS_HEADER_CARD_SIZE;
private static final DecimalFormatSymbols SYMBOLS = DecimalFormatSymbols.getInstance(Locale.US);
/**
* Sets the maximum number of decimal places to show after the leading
* figure (i.e. fractional digits in exponential format). If the value has
* more precision than this value it will be rounded to the specified
* decimal place. The special value {@link #AUTO_PRECISION} can be used to
* display as many of the available decimal places as can fit into the space
* that is available (see {@link #setWidth(int)}.
*
* @param nDecimals
* the requested new number of decimal places to show after the
* leading figure, or {@link #AUTO_PRECISION}. If an explicit
* value is set, all decimal values will be printed in
* exponential format with up to that many fractional digits
* showing before the exponent symbol.
* @return itself
* @see #autoPrecision()
* @see #getPrecision()
* @see #setWidth(int)
* @see #format(Number)
*/
public synchronized FlexFormat setPrecision(int nDecimals) {
decimals = nDecimals < 0 ? AUTO_PRECISION : nDecimals;
return this;
}
/**
* Selects flexible precision formatting of floating point values. The
* values will be printed either in fixed format or exponential format, with
* up to the number of decimal places supported by the underlying value. For
* {@link BigDecimal} and {@link BigInteger} types, the precision may be
* reduced at most down to {@link #DOUBLE_DECIMALS} to make it fit in the
* available space.
*
* @return itself
* @see #setPrecision(int)
* @see #getPrecision()
* @see #setWidth(int)
* @see #format(Number)
*/
public synchronized FlexFormat autoPrecision() {
return setPrecision(AUTO_PRECISION);
}
/**
* Returns the maximum number of decimal places that will be shown when
* formatting floating point values in exponential form, or
* {@link #AUTO_PRECISION} if either fixed or exponential form may be used
* with up to the native precision of the value, or whatever precision can
* be shown in the space available.
*
* @return the maximum number of decimal places that will be shown when
* formatting floating point values, or {@link #AUTO_PRECISION}.
* @see #setPrecision(int)
* @see #autoPrecision()
* @see #setWidth(int)
*/
public final synchronized int getPrecision() {
return decimals;
}
/**
* Sets the number of characters that this formatter can use to print number
* values. Subsequent calls to {@link #format(Number)} will guarantee to
* return only values that are shorter or equals to the specified width, or
* else throw an exception.
*
* @param nChars
* the new maximum length for formatted values.
* @return itself
* @see #getWidth()
* @see #forCard(HeaderCard)
* @see #setPrecision(int)
* @see #format(Number)
*/
public synchronized FlexFormat setWidth(int nChars) {
width = nChars > 0 ? nChars : 0;
return this;
}
/**
* Sets the number of characters that this formatter can use to print number
* values to the space available for the value field in the specified header
* card. It is essentially a shorthand for
* setWidth(card.spaceForValue()).
*
* @param card
* the header card in which the formatted number values must fit.
* @return itself
*/
public final FlexFormat forCard(HeaderCard card) {
return setWidth(card.spaceForValue());
}
/**
* Returns the number of characters that this formatter can use to print
* number values
*
* @return the maximum length for formatted values.
*/
public final synchronized int getWidth() {
return width;
}
/**
* Checks if the specified number is a decimal (non-integer) type.
*
* @param value
* the number to check
* @return true if the specified number is a decimal type
* value, or else false if it is an integer type.
*/
private static boolean isDecimal(Number value) {
return value instanceof Float || value instanceof Double || value instanceof BigDecimal;
}
/**
* Returns a string representation of a decimal number, in the available
* space, using either fixed decimal format or exponential notitation. It
* will use the notation that either gets closer to the required fixed
* precision while filling the available space, or if both notations can fit
* it will return the more compact one. If neither notation can be
* accomodated in the space available, then an exception is thrown.
*
* @param value
* the decimal value to print
* @return the string representing the value, or an empty string if the
* value was null.
* @throws LongValueException
* if the decimal value cannot be represented in the alotted
* space with any precision
* @see #setPrecision(int)
* @see #setWidth(int)
* @see #forCard(HeaderCard)
*/
public synchronized String format(Number value) throws LongValueException {
if (value == null) {
return "";
}
// The value in fixed notation...
String fixed = null;
if (!isDecimal(value)) {
// For integer types, always consider the fixed format...
fixed = value.toString();
if (fixed.length() <= width) {
return fixed;
}
if (!(value instanceof BigInteger)) {
throw new LongValueException(width, fixed);
}
// We'll try exponential with reduced precision...
fixed = null;
} else if (decimals < 0) {
// Don"t do fixed format if precision is set explicitly
// (It's not really trivial to control the number of significant
// gigures in the fixed format...)
double a = Math.abs(value.doubleValue());
if (a >= MIN_FIXED && a < MAX_FIXED) {
// Fixed format only in a resonable data...
try {
fixed = format(value, "0.#", AUTO_PRECISION, false);
} catch (LongValueException e) {
// We'll try with exponential notation...
}
}
}
// The value in exponential notation...
String exp = null;
try {
exp = format(value, "0.#E0", decimals, FitsFactory.isUseExponentD());
if (fixed == null) {
return exp;
}
// Go with whichever is more compact.
return exp.length() < fixed.length() ? exp : fixed;
} catch (LongValueException e) {
if (fixed == null) {
throw e;
}
}
return fixed;
}
/**
* Returns a fixed decimal representation of a value in the available space.
* For BigInteger and BigDecimal types, we allow reducing the precision at
* most down to to doube precision, if necessary to fit the number in the
* alotted space. If it's not at all possible to fit the fixed
* representation in the space available, then an exception is.
*
* @param value
* the decimal value to set
* @param fmt
* the string that describes the base format (e.g. "0.#" or
* "0E0").
* @param nDecimals
* the number of decimal places to show
* @param allowUseD
* if 'D' may be used instead of 'E' to precede the exponent when
* value has more than 32-bit floating-point precision.
* @return the fixed format decimal representation of the value in the
* alotted space.
* @throws LongValueException
* if the decimal value cannot be represented in the alotted
* space with the specified precision
*/
private synchronized String format(Number value, String fmt, int nDecimals, boolean allowUseD) throws LongValueException {
if (width < 1) {
throw new LongValueException(width);
}
DecimalFormat f = new DecimalFormat(fmt);
f.setDecimalFormatSymbols(SYMBOLS);
f.setDecimalSeparatorAlwaysShown(true);
f.setRoundingMode(RoundingMode.HALF_UP);
if (nDecimals < 0) {
// Determine precision based on the type.
if (value instanceof BigDecimal || value instanceof BigInteger) {
nDecimals = width;
} else if (value instanceof Double) {
nDecimals = DOUBLE_DECIMALS;
} else {
nDecimals = FLOAT_DECIMALS;
}
}
f.setMinimumFractionDigits(fmt.indexOf('E') < 0 ? 1 : 0);
f.setMaximumFractionDigits(nDecimals);
String text = f.format(value);
// Iterate to make sure we get where we want...
while (text.length() > width) {
int delta = text.length() - width;
nDecimals -= delta;
if ((value instanceof BigInteger && nDecimals < MIN_BIGINT_EFORM_DECIMALS) || (!(value instanceof BigInteger) && nDecimals < DOUBLE_DECIMALS)) {
// We cannot show enough decimals for big types...
throw new LongValueException(width, text);
}
f.setMaximumFractionDigits(nDecimals);
text = f.format(value);
}
if (allowUseD && nDecimals > FLOAT_DECIMALS) {
// If we want 'D' instead of 'E', just replace the letter in the
// result.
text = text.replace('E', 'D');
}
return text;
}
}