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

org.apache.commons.text.numbers.DoubleFormat Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.commons.text.numbers;

import java.text.DecimalFormatSymbols;
import java.util.Objects;
import java.util.function.DoubleFunction;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * Enum containing standard double format types with methods to produce
 * configured formatter instances. This type is intended to provide a
 * quick and convenient way to create lightweight, thread-safe double format functions
 * for common format types using a builder pattern. Output can be localized by
 * passing a {@link DecimalFormatSymbols} instance to the
 * {@link Builder#formatSymbols(DecimalFormatSymbols) formatSymbols} method or by
 * directly calling the various other builder configuration methods, such as
 * {@link Builder#digits(String) digits}.
 *
 * 

Comparison with DecimalFormat

*

* This type provides some of the same functionality as Java's own * {@link java.text.DecimalFormat}. However, unlike {@code DecimalFormat}, the format * functions produced by this type are lightweight and thread-safe, making them * much easier to work with in multi-threaded environments. They also provide performance * comparable to, and in many cases faster than, {@code DecimalFormat}. *

*

* It should be noted that the output {@code String} is created by formatting the output of * {@link Double#toString()}. This limits the output precision to the precision required * to exactly represent the input {@code double} and is dependent on the JDK implementation * of {@link Double#toString()}. A number formatted with the maximum * precision should be parsed to the same input {@code double}. This implementation * cannot extend the {@code String} to the required length to represent the exact decimal * value of the {@code double} as per * {@link java.math.BigDecimal#toString() BigDecimal#toString()}. *

*

Examples

*
 * // construct a formatter equivalent to Double.toString()
 * DoubleFunction<String> fmt = DoubleFormat.MIXED.builder().build();
 *
 * // construct a formatter equivalent to Double.toString() but using
 * // format symbols for a specific locale
 * DoubleFunction<String> fmt = DoubleFormat.MIXED.builder()
 *      .formatSymbols(DecimalFormatSymbols.getInstance(locale))
 *      .build();
 *
 * // construct a formatter equivalent to the DecimalFormat pattern "0.0##"
 * DoubleFunction<String> fmt = DoubleFormat.PLAIN.builder()
 *      .minDecimalExponent(-3)
 *      .build();
 *
 * // construct a formatter equivalent to the DecimalFormat pattern "#,##0.0##",
 * // where whole number groups of thousands are separated
 * DoubleFunction<String> fmt = DoubleFormat.PLAIN.builder()
 *      .minDecimalExponent(-3)
 *      .groupThousands(true)
 *      .build();
 *
 * // construct a formatter equivalent to the DecimalFormat pattern "0.0##E0"
 * DoubleFunction<String> fmt = DoubleFormat.SCIENTIFIC.builder()
 *      .maxPrecision(4)
 *      .alwaysIncludeExponent(true)
 *      .build()
 *
 * // construct a formatter equivalent to the DecimalFormat pattern "##0.0##E0",
 * // i.e. "engineering format"
 * DoubleFunction<String> fmt = DoubleFormat.ENGINEERING.builder()
 *      .maxPrecision(6)
 *      .alwaysIncludeExponent(true)
 *      .build()
 * 
* *

Implementation Notes

*

* {@link java.math.RoundingMode#HALF_EVEN Half-even} rounding is used in cases where the * decimal value must be rounded in order to meet the configuration requirements of the formatter * instance. *

* * @since 1.10.0 */ public enum DoubleFormat { /** * Number format without exponents. *

* For example: *

* *
     * 0.0
     * 12.401
     * 100000.0
     * 1450000000.0
     * 0.0000000000123
     * 
*/ PLAIN(PlainDoubleFormat::new), /** * Number format that uses exponents and contains a single digit to the left of the decimal point. *

* For example: *

* *
     * 0.0
     * 1.2401E1
     * 1.0E5
     * 1.45E9
     * 1.23E-11
     * 
*/ SCIENTIFIC(ScientificDoubleFormat::new), /** * Number format similar to {@link #SCIENTIFIC scientific format} but adjusted so that the exponent value is always a multiple of 3, allowing easier * alignment with SI prefixes. *

* For example: *

* *
     * 0.0
     * 12.401
     * 100.0E3
     * 1.45E9
     * 12.3E-12
     * 
*/ ENGINEERING(EngineeringDoubleFormat::new), /** * Number format that uses {@link #PLAIN plain format} for small numbers and {@link #SCIENTIFIC scientific format} for large numbers. The number thresholds * can be configured through the {@link Builder#plainFormatMinDecimalExponent(int) plainFormatMinDecimalExponent} and * {@link Builder#plainFormatMaxDecimalExponent(int) plainFormatMaxDecimalExponent} properties. *

* For example: *

* *
     * 0.0
     * 12.401
     * 100000.0
     * 1.45E9
     * 1.23E-11
     * 
*/ MIXED(MixedDoubleFormat::new); /** * Base class for standard double formatting classes. */ private abstract static class AbstractDoubleFormat implements DoubleFunction, ParsedDecimal.FormatOptions { /** Maximum precision; 0 indicates no limit. */ private final int maxPrecision; /** Minimum decimal exponent. */ private final int minDecimalExponent; /** String representing positive infinity. */ private final String positiveInfinity; /** String representing negative infinity. */ private final String negativeInfinity; /** String representing NaN. */ private final String nan; /** Flag determining if fraction placeholders should be used. */ private final boolean fractionPlaceholder; /** Flag determining if signed zero strings are allowed. */ private final boolean signedZero; /** String containing the digits 0-9. */ private final char[] digits; /** Decimal separator character. */ private final char decimalSeparator; /** Thousands grouping separator. */ private final char groupingSeparator; /** Flag indicating if thousands should be grouped. */ private final boolean groupThousands; /** Minus sign character. */ private final char minusSign; /** Exponent separator character. */ private final char[] exponentSeparatorChars; /** Flag indicating if exponent values should always be included, even if zero. */ private final boolean alwaysIncludeExponent; /** * Constructs a new instance. * * @param builder builder instance containing configuration values */ AbstractDoubleFormat(final Builder builder) { this.maxPrecision = builder.maxPrecision; this.minDecimalExponent = builder.minDecimalExponent; this.positiveInfinity = builder.infinity; this.negativeInfinity = builder.minusSign + builder.infinity; this.nan = builder.nan; this.fractionPlaceholder = builder.fractionPlaceholder; this.signedZero = builder.signedZero; this.digits = builder.digits.toCharArray(); this.decimalSeparator = builder.decimalSeparator; this.groupingSeparator = builder.groupingSeparator; this.groupThousands = builder.groupThousands; this.minusSign = builder.minusSign; this.exponentSeparatorChars = builder.exponentSeparator.toCharArray(); this.alwaysIncludeExponent = builder.alwaysIncludeExponent; } /** {@inheritDoc} */ @Override public String apply(final double d) { if (Double.isFinite(d)) { return applyFinite(d); } if (Double.isInfinite(d)) { return d > 0.0 ? positiveInfinity : negativeInfinity; } return nan; } /** * Returns a formatted string representation of the given finite value. * * @param d double value */ private String applyFinite(final double d) { final ParsedDecimal n = ParsedDecimal.from(d); int roundExponent = Math.max(n.getExponent(), minDecimalExponent); if (maxPrecision > 0) { roundExponent = Math.max(n.getScientificExponent() - maxPrecision + 1, roundExponent); } n.round(roundExponent); return applyFiniteInternal(n); } /** * Returns a formatted representation of the given rounded decimal value to {@code dst}. * * @param val value to format * @return a formatted representation of the given rounded decimal value to {@code dst}. */ protected abstract String applyFiniteInternal(ParsedDecimal val); /** {@inheritDoc} */ @Override public char getDecimalSeparator() { return decimalSeparator; } /** {@inheritDoc} */ @Override public char[] getDigits() { return digits; } /** {@inheritDoc} */ @Override public char[] getExponentSeparatorChars() { return exponentSeparatorChars; } /** {@inheritDoc} */ @Override public char getGroupingSeparator() { return groupingSeparator; } /** {@inheritDoc} */ @Override public char getMinusSign() { return minusSign; } /** {@inheritDoc} */ @Override public boolean isAlwaysIncludeExponent() { return alwaysIncludeExponent; } /** {@inheritDoc} */ @Override public boolean isGroupThousands() { return groupThousands; } /** {@inheritDoc} */ @Override public boolean isIncludeFractionPlaceholder() { return fractionPlaceholder; } /** {@inheritDoc} */ @Override public boolean isSignedZero() { return signedZero; } } /** * Builds configured format functions for standard double format types. */ public static final class Builder implements Supplier> { /** Default value for the plain format max decimal exponent. */ private static final int DEFAULT_PLAIN_FORMAT_MAX_DECIMAL_EXPONENT = 6; /** Default value for the plain format min decimal exponent. */ private static final int DEFAULT_PLAIN_FORMAT_MIN_DECIMAL_EXPONENT = -3; /** Default decimal digit characters. */ private static final String DEFAULT_DECIMAL_DIGITS = "0123456789"; /** * Gets a string containing the localized digits 0-9 for the given symbols object. The string is constructed by starting at the * {@link DecimalFormatSymbols#getZeroDigit() zero digit} and adding the next 9 consecutive characters. * * @param symbols symbols object * @return string containing the localized digits 0-9 */ private static String getDigitString(final DecimalFormatSymbols symbols) { final int zeroDelta = symbols.getZeroDigit() - DEFAULT_DECIMAL_DIGITS.charAt(0); final char[] digitChars = new char[DEFAULT_DECIMAL_DIGITS.length()]; for (int i = 0; i < DEFAULT_DECIMAL_DIGITS.length(); ++i) { digitChars[i] = (char) (DEFAULT_DECIMAL_DIGITS.charAt(i) + zeroDelta); } return String.valueOf(digitChars); } /** Function used to construct format instances. */ private final Function> factory; /** Maximum number of significant decimal digits in formatted strings. */ private int maxPrecision; /** Minimum decimal exponent. */ private int minDecimalExponent = Integer.MIN_VALUE; /** Max decimal exponent to use with plain formatting with the mixed format type. */ private int plainFormatMaxDecimalExponent = DEFAULT_PLAIN_FORMAT_MAX_DECIMAL_EXPONENT; /** Min decimal exponent to use with plain formatting with the mixed format type. */ private int plainFormatMinDecimalExponent = DEFAULT_PLAIN_FORMAT_MIN_DECIMAL_EXPONENT; /** String representing infinity. */ private String infinity = "Infinity"; /** String representing NaN. */ private String nan = "NaN"; /** Flag determining if fraction placeholders should be used. */ private boolean fractionPlaceholder = true; /** Flag determining if signed zero strings are allowed. */ private boolean signedZero = true; /** String of digit characters 0-9. */ private String digits = DEFAULT_DECIMAL_DIGITS; /** Decimal separator character. */ private char decimalSeparator = '.'; /** Character used to separate groups of thousands. */ private char groupingSeparator = ','; /** If {@code true}, thousands groups will be separated by the grouping separator. */ private boolean groupThousands; /** Minus sign character. */ private char minusSign = '-'; /** Exponent separator character. */ private String exponentSeparator = "E"; /** Flag indicating if the exponent value should always be included, even if zero. */ private boolean alwaysIncludeExponent; /** * Builds a new instance that delegates double function construction to the given factory object. * * @param factory factory function */ private Builder(final Function> factory) { this.factory = factory; } /** * Sets the flag determining whether or not the zero string may be returned with the minus sign or if it will always be returned in the positive form. * For example, if set to {@code true}, the string {@code "-0.0"} may be returned for some input numbers. If {@code false}, only {@code "0.0"} will be * returned, regardless of the sign of the input number. The default value is {@code true}. * * @param signedZero if {@code true}, the zero string may be returned with a preceding minus sign; if {@code false}, the zero string will only be * returned in its positive form * @return this instance */ public Builder allowSignedZero(final boolean signedZero) { this.signedZero = signedZero; return this; } /** * Sets the flag indicating if an exponent value should always be included in the formatted value, even if the exponent value is zero. This property * only applies to formats that use scientific notation, namely {@link DoubleFormat#SCIENTIFIC SCIENTIFIC}, {@link DoubleFormat#ENGINEERING * ENGINEERING}, and {@link DoubleFormat#MIXED MIXED}. The default value is {@code false}. * * @param alwaysIncludeExponent if {@code true}, exponents will always be included in formatted output even if the exponent value is zero * @return this instance */ public Builder alwaysIncludeExponent(final boolean alwaysIncludeExponent) { this.alwaysIncludeExponent = alwaysIncludeExponent; return this; } /** * Builds a new double format function. * * @return format function * @deprecated Use {@link #get()}. */ @Deprecated public DoubleFunction build() { return get(); } /** * Sets the decimal separator character, i.e., the character placed between the whole number and fractional portions of the formatted strings. The * default value is {@code '.'}. * * @param decimalSeparator decimal separator character * @return this instance */ public Builder decimalSeparator(final char decimalSeparator) { this.decimalSeparator = decimalSeparator; return this; } /** * Sets the string containing the digit characters 0-9, in that order. The default value is the string {@code "0123456789"}. * * @param digits string containing the digit characters 0-9 * @return this instance * @throws NullPointerException if the argument is {@code null} * @throws IllegalArgumentException if the argument does not have a length of exactly 10 */ public Builder digits(final String digits) { Objects.requireNonNull(digits, "digits"); if (digits.length() != DEFAULT_DECIMAL_DIGITS.length()) { throw new IllegalArgumentException("Digits string must contain exactly " + DEFAULT_DECIMAL_DIGITS.length() + " characters."); } this.digits = digits; return this; } /** * Sets the exponent separator character, i.e., the string placed between the mantissa and the exponent. The default value is {@code "E"}, as in * {@code "1.2E6"}. * * @param exponentSeparator exponent separator string * @return this instance * @throws NullPointerException if the argument is {@code null} */ public Builder exponentSeparator(final String exponentSeparator) { this.exponentSeparator = Objects.requireNonNull(exponentSeparator, "exponentSeparator"); return this; } /** * Configures this instance with the given format symbols. The following values are set: *
    *
  • {@link #digits(String) digit characters}
  • *
  • {@link #decimalSeparator(char) decimal separator}
  • *
  • {@link #groupingSeparator(char) thousands grouping separator}
  • *
  • {@link #minusSign(char) minus sign}
  • *
  • {@link #exponentSeparator(String) exponent separator}
  • *
  • {@link #infinity(String) infinity}
  • *
  • {@link #nan(String) NaN}
  • *
* The digit character string is constructed by starting at the configured {@link DecimalFormatSymbols#getZeroDigit() zero digit} and adding the next 9 * consecutive characters. * * @param symbols format symbols * @return this instance * @throws NullPointerException if the argument is {@code null} */ public Builder formatSymbols(final DecimalFormatSymbols symbols) { Objects.requireNonNull(symbols, "symbols"); return digits(getDigitString(symbols)).decimalSeparator(symbols.getDecimalSeparator()).groupingSeparator(symbols.getGroupingSeparator()) .minusSign(symbols.getMinusSign()).exponentSeparator(symbols.getExponentSeparator()).infinity(symbols.getInfinity()).nan(symbols.getNaN()); } /** * Builds a new double format function. * * @return format function */ @Override public DoubleFunction get() { return factory.apply(this); } /** * Sets the character used to separate groups of thousands. Default value is {@code ','}. * * @param groupingSeparator character used to separate groups of thousands * @return this instance * @see #groupThousands(boolean) */ public Builder groupingSeparator(final char groupingSeparator) { this.groupingSeparator = groupingSeparator; return this; } /** * If set to {@code true}, thousands will be grouped with the {@link #groupingSeparator(char) grouping separator}. For example, if set to {@code true}, * the number {@code 1000} could be formatted as {@code "1,000"}. This property only applies to the {@link DoubleFormat#PLAIN PLAIN} format. Default * value is {@code false}. * * @param groupThousands if {@code true}, thousands will be grouped * @return this instance * @see #groupingSeparator(char) */ public Builder groupThousands(final boolean groupThousands) { this.groupThousands = groupThousands; return this; } /** * Sets the flag determining whether or not a zero character is added in the fraction position when no fractional value is present. For example, if set * to {@code true}, the number {@code 1} would be formatted as {@code "1.0"}. If {@code false}, it would be formatted as {@code "1"}. The default value * is {@code true}. * * @param fractionPlaceholder if {@code true}, a zero character is placed in the fraction position when no fractional value is present; if * {@code false}, fractional digits are only included when needed * @return this instance */ public Builder includeFractionPlaceholder(final boolean fractionPlaceholder) { this.fractionPlaceholder = fractionPlaceholder; return this; } /** * Sets the string used to represent infinity. For negative infinity, this string is prefixed with the {@link #minusSign(char) minus sign}. * * @param infinity string used to represent infinity * @return this instance * @throws NullPointerException if the argument is {@code null} */ public Builder infinity(final String infinity) { this.infinity = Objects.requireNonNull(infinity, "infinity"); return this; } /** * Sets the maximum number of significant decimal digits used in format results. A value of {@code 0} indicates no limit. The default value is * {@code 0}. * * @param maxPrecision maximum precision * @return this instance */ public Builder maxPrecision(final int maxPrecision) { this.maxPrecision = maxPrecision; return this; } /** * Sets the minimum decimal exponent for formatted strings. No digits with an absolute value of less than 10minDecimalExponent * will be included in format results. If the number being formatted does not contain any such digits, then zero is returned. For example, if * {@code minDecimalExponent} is set to {@code -2} and the number {@code 3.14159} is formatted, the plain format result will be {@code "3.14"}. If * {@code 0.001} is formatted, then the result is the zero string. * * @param minDecimalExponent minimum decimal exponent * @return this instance */ public Builder minDecimalExponent(final int minDecimalExponent) { this.minDecimalExponent = minDecimalExponent; return this; } /** * Sets the character used as the minus sign. * * @param minusSign character to use as the minus sign * @return this instance */ public Builder minusSign(final char minusSign) { this.minusSign = minusSign; return this; } /** * Sets the string used to represent {@link Double#NaN}. * * @param nan string used to represent {@link Double#NaN} * @return this instance * @throws NullPointerException if the argument is {@code null} */ public Builder nan(final String nan) { this.nan = Objects.requireNonNull(nan, "nan"); return this; } /** * Sets the maximum decimal exponent for numbers formatted as plain decimal strings when using the {@link DoubleFormat#MIXED MIXED} format type. If the * number being formatted has an absolute value less than 10plainFormatMaxDecimalExponent + 1 and greater than or equal to * 10plainFormatMinDecimalExponent after any necessary rounding, then the formatted result will use the * {@link DoubleFormat#PLAIN PLAIN} format type. Otherwise, {@link DoubleFormat#SCIENTIFIC SCIENTIFIC} format will be used. For example, if this value * is set to {@code 2}, the number {@code 999} will be formatted as {@code "999.0"} while {@code 1000} will be formatted as {@code "1.0E3"}. * *

* The default value is {@code 6}. * *

* This value is ignored for formats other than {@link DoubleFormat#MIXED}. * * @param plainFormatMaxDecimalExponent maximum decimal exponent for values formatted as plain strings when using the {@link DoubleFormat#MIXED MIXED} * format type. * @return this instance * @see #plainFormatMinDecimalExponent(int) */ public Builder plainFormatMaxDecimalExponent(final int plainFormatMaxDecimalExponent) { this.plainFormatMaxDecimalExponent = plainFormatMaxDecimalExponent; return this; } /** * Sets the minimum decimal exponent for numbers formatted as plain decimal strings when using the {@link DoubleFormat#MIXED MIXED} format type. If the * number being formatted has an absolute value less than 10plainFormatMaxDecimalExponent + 1 and greater than or equal to * 10plainFormatMinDecimalExponent after any necessary rounding, then the formatted result will use the * {@link DoubleFormat#PLAIN PLAIN} format type. Otherwise, {@link DoubleFormat#SCIENTIFIC SCIENTIFIC} format will be used. For example, if this value * is set to {@code -2}, the number {@code 0.01} will be formatted as {@code "0.01"} while {@code 0.0099} will be formatted as {@code "9.9E-3"}. * *

* The default value is {@code -3}. * *

* This value is ignored for formats other than {@link DoubleFormat#MIXED}. * * @param plainFormatMinDecimalExponent maximum decimal exponent for values formatted as plain strings when using the {@link DoubleFormat#MIXED MIXED} * format type. * @return this instance * @see #plainFormatMinDecimalExponent(int) */ public Builder plainFormatMinDecimalExponent(final int plainFormatMinDecimalExponent) { this.plainFormatMinDecimalExponent = plainFormatMinDecimalExponent; return this; } } /** * Format class that uses engineering notation for all values. */ private static final class EngineeringDoubleFormat extends AbstractDoubleFormat { /** * Constructs a new instance. * * @param builder builder instance containing configuration values */ EngineeringDoubleFormat(final Builder builder) { super(builder); } /** {@inheritDoc} */ @Override public String applyFiniteInternal(final ParsedDecimal val) { return val.toEngineeringString(this); } } /** * Format class producing results similar to {@link Double#toString()}, with plain decimal notation for small numbers relatively close to zero and * scientific notation otherwise. */ private static final class MixedDoubleFormat extends AbstractDoubleFormat { /** Max decimal exponent for plain format. */ private final int plainMaxExponent; /** Min decimal exponent for plain format. */ private final int plainMinExponent; /** * Constructs a new instance. * * @param builder builder instance containing configuration values */ MixedDoubleFormat(final Builder builder) { super(builder); this.plainMaxExponent = builder.plainFormatMaxDecimalExponent; this.plainMinExponent = builder.plainFormatMinDecimalExponent; } /** {@inheritDoc} */ @Override protected String applyFiniteInternal(final ParsedDecimal val) { final int sciExp = val.getScientificExponent(); if (sciExp <= plainMaxExponent && sciExp >= plainMinExponent) { return val.toPlainString(this); } return val.toScientificString(this); } } /** * Format class that produces plain decimal strings that do not use scientific notation. */ private static final class PlainDoubleFormat extends AbstractDoubleFormat { /** * Constructs a new instance. * * @param builder builder instance containing configuration values */ PlainDoubleFormat(final Builder builder) { super(builder); } /** * {@inheritDoc} */ @Override protected String applyFiniteInternal(final ParsedDecimal val) { return val.toPlainString(this); } } /** * Format class that uses scientific notation for all values. */ private static final class ScientificDoubleFormat extends AbstractDoubleFormat { /** * Constructs a new instance. * * @param builder builder instance containing configuration values */ ScientificDoubleFormat(final Builder builder) { super(builder); } /** {@inheritDoc} */ @Override public String applyFiniteInternal(final ParsedDecimal val) { return val.toScientificString(this); } } /** Function used to construct instances for this format type. */ private final Function> factory; /** * Constructs a new instance. * * @param factory function used to construct format instances */ DoubleFormat(final Function> factory) { this.factory = factory; } /** * Creates a {@link Builder} for building formatter functions for this format type. * * @return builder instance */ public Builder builder() { return new Builder(factory); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy