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

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

There is a newer version: 1.12.0
Show 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;

/**
 * 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}. * *

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. * Ex: *

     * 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. * Ex: *
     * 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. * Ex: *
     * 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 plainFormatMinDecimalExponent} * and * {@link Builder#plainFormatMaxDecimalExponent plainFormatMaxDecimalExponent} * properties. * Ex: *
     * 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 { /** 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"; /** Function used to construct format instances. */ private final Function> factory; /** Maximum number of significant decimal digits in formatted strings. */ private int maxPrecision = 0; /** 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 = false; /** 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 = false; /** * 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 */ public DoubleFunction build() { return factory.apply(this); } /** * 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 string cannot be null"); 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, "Exponent separator cannot be null"); 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, "Decimal format symbols cannot be null"); return digits(getDigitString(symbols)) .decimalSeparator(symbols.getDecimalSeparator()) .groupingSeparator(symbols.getGroupingSeparator()) .minusSign(symbols.getMinusSign()) .exponentSeparator(symbols.getExponentSeparator()) .infinity(symbols.getInfinity()) .nan(symbols.getNaN()); } /** * 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 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); } /** * 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 string cannot be null"); 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 string cannot be null"); 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 {@value #DEFAULT_PLAIN_FORMAT_MAX_DECIMAL_EXPONENT}. * *

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 {@value #DEFAULT_PLAIN_FORMAT_MIN_DECIMAL_EXPONENT}. * *

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 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 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 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