    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.text.numbers;
    018
    019import java.text.DecimalFormatSymbols;
    020import java.util.Objects;
    021import java.util.function.DoubleFunction;
    022import java.util.function.Function;
    023
    024/**
    025 * Enum containing standard double format types with methods to produce
    026 * configured formatter instances. This type is intended to provide a
    027 * quick and convenient way to create lightweight, thread-safe double format functions
    028 * for common format types using a builder pattern. Output can be localized by
    029 * passing a {@link DecimalFormatSymbols} instance to the
    030 * {@link Builder#formatSymbols(DecimalFormatSymbols) formatSymbols} method or by
    031 * directly calling the various other builder configuration methods, such as
    032 * {@link Builder#digits(String) digits}.
    033 *
    034 * <p><strong>Comparison with DecimalFormat</strong></p>
    035 * <p>
    036 * This type provides some of the same functionality as Java's own
    037 * {@link java.text.DecimalFormat}. However, unlike {@code DecimalFormat}, the format
    038 * functions produced by this type are lightweight and thread-safe, making them
    039 * much easier to work with in multi-threaded environments. They also provide performance
    040 * comparable to, and in many cases faster than, {@code DecimalFormat}.
    041 * </p>
    042 * <p>
    043 * It should be noted that the output {@code String} is created by formatting the output of
    044 * {@link Double#toString()}. This limits the output precision to the precision required
    045 * to exactly represent the input {@code double} and is dependent on the JDK implementation
    046 * of {@link Double#toString()}. A number formatted with the maximum
    047 * precision should be parsed to the same input {@code double}. This implementation
    048 * cannot extend the {@code String} to the required length to represent the exact decimal
    049 * value of the {@code double} as per
    050 * {@link java.math.BigDecimal#toString() BigDecimal#toString()}.
    051 * </p>
    052 * <p><strong>Examples</strong></p>
    053 * <pre>
    054 * // construct a formatter equivalent to Double.toString()
    055 * DoubleFunction&lt;String&gt; fmt = DoubleFormat.MIXED.builder().build();
    056 *
    057 * // construct a formatter equivalent to Double.toString() but using
    058 * // format symbols for a specific locale
    059 * DoubleFunction&lt;String&gt; fmt = DoubleFormat.MIXED.builder()
    060 *      .formatSymbols(DecimalFormatSymbols.getInstance(locale))
    061 *      .build();
    062 *
    063 * // construct a formatter equivalent to the DecimalFormat pattern "0.0##"
    064 * DoubleFunction&lt;String&gt; fmt = DoubleFormat.PLAIN.builder()
    065 *      .minDecimalExponent(-3)
    066 *      .build();
    067 *
    068 * // construct a formatter equivalent to the DecimalFormat pattern "#,##0.0##",
    069 * // where whole number groups of thousands are separated
    070 * DoubleFunction&lt;String&gt; fmt = DoubleFormat.PLAIN.builder()
    071 *      .minDecimalExponent(-3)
    072 *      .groupThousands(true)
    073 *      .build();
    074 *
    075 * // construct a formatter equivalent to the DecimalFormat pattern "0.0##E0"
    076 * DoubleFunction&lt;String&gt; fmt = DoubleFormat.SCIENTIFIC.builder()
    077 *      .maxPrecision(4)
    078 *      .alwaysIncludeExponent(true)
    079 *      .build()
    080 *
    081 * // construct a formatter equivalent to the DecimalFormat pattern "##0.0##E0",
    082 * // i.e. "engineering format"
    083 * DoubleFunction&lt;String&gt; fmt = DoubleFormat.ENGINEERING.builder()
    084 *      .maxPrecision(6)
    085 *      .alwaysIncludeExponent(true)
    086 *      .build()
    087 * </pre>
    088 *
    089 * <p><strong>Implementation Notes</strong></p>
    090 * <p>
    091 * {@link java.math.RoundingMode#HALF_EVEN Half-even} rounding is used in cases where the
    092 * decimal value must be rounded in order to meet the configuration requirements of the formatter
    093 * instance.
    094 * </p>
    095 *
    096 * @since 1.10.0
    097 */
    098public enum DoubleFormat {
    099
    100    /**
    101     * Number format without exponents.
    102     * <p>
    103     * For example:
    104     * </p>
    105     *
    106     * <pre>
    107     * 0.0
    108     * 12.401
    109     * 100000.0
    110     * 1450000000.0
    111     * 0.0000000000123
    112     * </pre>
    113     */
    114    PLAIN(PlainDoubleFormat::new),
    115
    116    /**
    117     * Number format that uses exponents and contains a single digit to the left of the decimal point.
    118     * <p>
    119     * For example:
    120     * </p>
    121     *
    122     * <pre>
    123     * 0.0
    124     * 1.2401E1
    125     * 1.0E5
    126     * 1.45E9
    127     * 1.23E-11
    128     * </pre>
    129     */
    130    SCIENTIFIC(ScientificDoubleFormat::new),
    131
    132    /**
    133     * Number format similar to {@link #SCIENTIFIC scientific format} but adjusted so that the exponent value is always a multiple of 3, allowing easier
    134     * alignment with SI prefixes.
    135     * <p>
    136     * For example:
    137     * </p>
    138     *
    139     * <pre>
    140     * 0.0
    141     * 12.401
    142     * 100.0E3
    143     * 1.45E9
    144     * 12.3E-12
    145     * </pre>
    146     */
    147    ENGINEERING(EngineeringDoubleFormat::new),
    148
    149    /**
    150     * Number format that uses {@link #PLAIN plain format} for small numbers and {@link #SCIENTIFIC scientific format} for large numbers. The number thresholds
    151     * can be configured through the {@link Builder#plainFormatMinDecimalExponent(int) plainFormatMinDecimalExponent} and
    152     * {@link Builder#plainFormatMaxDecimalExponent(int) plainFormatMaxDecimalExponent} properties.
    153     * <p>
    154     * For example:
    155     * </p>
    156     *
    157     * <pre>
    158     * 0.0
    159     * 12.401
    160     * 100000.0
    161     * 1.45E9
    162     * 1.23E-11
    163     * </pre>
    164     */
    165    MIXED(MixedDoubleFormat::new);
    166
    167    /**
    168     * Base class for standard double formatting classes.
    169     */
    170    private abstract static class AbstractDoubleFormat implements DoubleFunction<String>, ParsedDecimal.FormatOptions {
    171
    172        /** Maximum precision; 0 indicates no limit. */
    173        private final int maxPrecision;
    174
    175        /** Minimum decimal exponent. */
    176        private final int minDecimalExponent;
    177
    178        /** String representing positive infinity. */
    179        private final String positiveInfinity;
    180
    181        /** String representing negative infinity. */
    182        private final String negativeInfinity;
    183
    184        /** String representing NaN. */
    185        private final String nan;
    186
    187        /** Flag determining if fraction placeholders should be used. */
    188        private final boolean fractionPlaceholder;
    189
    190        /** Flag determining if signed zero strings are allowed. */
    191        private final boolean signedZero;
    192
    193        /** String containing the digits 0-9. */
    194        private final char[] digits;
    195
    196        /** Decimal separator character. */
    197        private final char decimalSeparator;
    198
    199        /** Thousands grouping separator. */
    200        private final char groupingSeparator;
    201
    202        /** Flag indicating if thousands should be grouped. */
    203        private final boolean groupThousands;
    204
    205        /** Minus sign character. */
    206        private final char minusSign;
    207
    208        /** Exponent separator character. */
    209        private final char[] exponentSeparatorChars;
    210
    211        /** Flag indicating if exponent values should always be included, even if zero. */
    212        private final boolean alwaysIncludeExponent;
    213
    214        /**
    215         * Constructs a new instance.
    216         *
    217         * @param builder builder instance containing configuration values
    218         */
    219        AbstractDoubleFormat(final Builder builder) {
    220            this.maxPrecision = builder.maxPrecision;
    221            this.minDecimalExponent = builder.minDecimalExponent;
    222
    223            this.positiveInfinity = builder.infinity;
    224            this.negativeInfinity = builder.minusSign + builder.infinity;
    225            this.nan = builder.nan;
    226
    227            this.fractionPlaceholder = builder.fractionPlaceholder;
    228            this.signedZero = builder.signedZero;
    229            this.digits = builder.digits.toCharArray();
    230            this.decimalSeparator = builder.decimalSeparator;
    231            this.groupingSeparator = builder.groupingSeparator;
    232            this.groupThousands = builder.groupThousands;
    233            this.minusSign = builder.minusSign;
    234            this.exponentSeparatorChars = builder.exponentSeparator.toCharArray();
    235            this.alwaysIncludeExponent = builder.alwaysIncludeExponent;
    236        }
    237
    238        /** {@inheritDoc} */
    239        @Override
    240        public String apply(final double d) {
    241            if (Double.isFinite(d)) {
    242                return applyFinite(d);
    243            }
    244            if (Double.isInfinite(d)) {
    245                return d > 0.0 ? positiveInfinity : negativeInfinity;
    246            }
    247            return nan;
    248        }
    249
    250        /**
    251         * Returns a formatted string representation of the given finite value.
    252         *
    253         * @param d double value
    254         */
    255        private String applyFinite(final double d) {
    256            final ParsedDecimal n = ParsedDecimal.from(d);
    257
    258            int roundExponent = Math.max(n.getExponent(), minDecimalExponent);
    259            if (maxPrecision > 0) {
    260                roundExponent = Math.max(n.getScientificExponent() - maxPrecision + 1, roundExponent);
    261            }
    262            n.round(roundExponent);
    263
    264            return applyFiniteInternal(n);
    265        }
    266
    267        /**
    268         * Returns a formatted representation of the given rounded decimal value to {@code dst}.
    269         *
    270         * @param val value to format
    271         * @return a formatted representation of the given rounded decimal value to {@code dst}.
    272         */
    273        protected abstract String applyFiniteInternal(ParsedDecimal val);
    274
    275        /** {@inheritDoc} */
    276        @Override
    277        public char getDecimalSeparator() {
    278            return decimalSeparator;
    279        }
    280
    281        /** {@inheritDoc} */
    282        @Override
    283        public char[] getDigits() {
    284            return digits;
    285        }
    286
    287        /** {@inheritDoc} */
    288        @Override
    289        public char[] getExponentSeparatorChars() {
    290            return exponentSeparatorChars;
    291        }
    292
    293        /** {@inheritDoc} */
    294        @Override
    295        public char getGroupingSeparator() {
    296            return groupingSeparator;
    297        }
    298
    299        /** {@inheritDoc} */
    300        @Override
    301        public char getMinusSign() {
    302            return minusSign;
    303        }
    304
    305        /** {@inheritDoc} */
    306        @Override
    307        public boolean isAlwaysIncludeExponent() {
    308            return alwaysIncludeExponent;
    309        }
    310
    311        /** {@inheritDoc} */
    312        @Override
    313        public boolean isGroupThousands() {
    314            return groupThousands;
    315        }
    316
    317        /** {@inheritDoc} */
    318        @Override
    319        public boolean isIncludeFractionPlaceholder() {
    320            return fractionPlaceholder;
    321        }
    322
    323        /** {@inheritDoc} */
    324        @Override
    325        public boolean isSignedZero() {
    326            return signedZero;
    327        }
    328    }
    329
    330    /**
    331     * Builds configured format functions for standard double format types.
    332     */
    333    public static final class Builder {
    334
    335        /** Default value for the plain format max decimal exponent. */
    336        private static final int DEFAULT_PLAIN_FORMAT_MAX_DECIMAL_EXPONENT = 6;
    337
    338        /** Default value for the plain format min decimal exponent. */
    339        private static final int DEFAULT_PLAIN_FORMAT_MIN_DECIMAL_EXPONENT = -3;
    340
    341        /** Default decimal digit characters. */
    342        private static final String DEFAULT_DECIMAL_DIGITS = "0123456789";
    343
    344        /**
    345         * Gets a string containing the localized digits 0-9 for the given symbols object. The string is constructed by starting at the
    346         * {@link DecimalFormatSymbols#getZeroDigit() zero digit} and adding the next 9 consecutive characters.
    347         *
    348         * @param symbols symbols object
    349         * @return string containing the localized digits 0-9
    350         */
    351        private static String getDigitString(final DecimalFormatSymbols symbols) {
    352            final int zeroDelta = symbols.getZeroDigit() - DEFAULT_DECIMAL_DIGITS.charAt(0);
    353
    354            final char[] digitChars = new char[DEFAULT_DECIMAL_DIGITS.length()];
    355            for (int i = 0; i < DEFAULT_DECIMAL_DIGITS.length(); ++i) {
    356                digitChars[i] = (char) (DEFAULT_DECIMAL_DIGITS.charAt(i) + zeroDelta);
    357            }
    358
    359            return String.valueOf(digitChars);
    360        }
    361
    362        /** Function used to construct format instances. */
    363        private final Function<Builder, DoubleFunction<String>> factory;
    364
    365        /** Maximum number of significant decimal digits in formatted strings. */
    366        private int maxPrecision = 0;
    367
    368        /** Minimum decimal exponent. */
    369        private int minDecimalExponent = Integer.MIN_VALUE;
    370
    371        /** Max decimal exponent to use with plain formatting with the mixed format type. */
    372        private int plainFormatMaxDecimalExponent = DEFAULT_PLAIN_FORMAT_MAX_DECIMAL_EXPONENT;
    373
    374        /** Min decimal exponent to use with plain formatting with the mixed format type. */
    375        private int plainFormatMinDecimalExponent = DEFAULT_PLAIN_FORMAT_MIN_DECIMAL_EXPONENT;
    376
    377        /** String representing infinity. */
    378        private String infinity = "Infinity";
    379
    380        /** String representing NaN. */
    381        private String nan = "NaN";
    382
    383        /** Flag determining if fraction placeholders should be used. */
    384        private boolean fractionPlaceholder = true;
    385
    386        /** Flag determining if signed zero strings are allowed. */
    387        private boolean signedZero = true;
    388
    389        /** String of digit characters 0-9. */
    390        private String digits = DEFAULT_DECIMAL_DIGITS;
    391
    392        /** Decimal separator character. */
    393        private char decimalSeparator = '.';
    394
    395        /** Character used to separate groups of thousands. */
    396        private char groupingSeparator = ',';
    397
    398        /** If {@code true}, thousands groups will be separated by the grouping separator. */
    399        private boolean groupThousands = false;
    400
    401        /** Minus sign character. */
    402        private char minusSign = '-';
    403
    404        /** Exponent separator character. */
    405        private String exponentSeparator = "E";
    406
    407        /** Flag indicating if the exponent value should always be included, even if zero. */
    408        private boolean alwaysIncludeExponent = false;
    409
    410        /**
    411         * Builds a new instance that delegates double function construction to the given factory object.
    412         *
    413         * @param factory factory function
    414         */
    415        private Builder(final Function<Builder, DoubleFunction<String>> factory) {
    416            this.factory = factory;
    417        }
    418
    419        /**
    420         * 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.
    421         * 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
    422         * returned, regardless of the sign of the input number. The default value is {@code true}.
    423         *
    424         * @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
    425         *                   returned in its positive form
    426         * @return this instance
    427         */
    428        public Builder allowSignedZero(final boolean signedZero) {
    429            this.signedZero = signedZero;
    430            return this;
    431        }
    432
    433        /**
    434         * 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
    435         * only applies to formats that use scientific notation, namely {@link DoubleFormat#SCIENTIFIC SCIENTIFIC}, {@link DoubleFormat#ENGINEERING
    436         * ENGINEERING}, and {@link DoubleFormat#MIXED MIXED}. The default value is {@code false}.
    437         *
    438         * @param alwaysIncludeExponent if {@code true}, exponents will always be included in formatted output even if the exponent value is zero
    439         * @return this instance
    440         */
    441        public Builder alwaysIncludeExponent(final boolean alwaysIncludeExponent) {
    442            this.alwaysIncludeExponent = alwaysIncludeExponent;
    443            return this;
    444        }
    445
    446        /**
    447         * Builds a new double format function.
    448         *
    449         * @return format function
    450         */
    451        public DoubleFunction<String> build() {
    452            return factory.apply(this);
    453        }
    454
    455        /**
    456         * Sets the decimal separator character, i.e., the character placed between the whole number and fractional portions of the formatted strings. The
    457         * default value is {@code '.'}.
    458         *
    459         * @param decimalSeparator decimal separator character
    460         * @return this instance
    461         */
    462        public Builder decimalSeparator(final char decimalSeparator) {
    463            this.decimalSeparator = decimalSeparator;
    464            return this;
    465        }
    466
    467        /**
    468         * Sets the string containing the digit characters 0-9, in that order. The default value is the string {@code "0123456789"}.
    469         *
    470         * @param digits string containing the digit characters 0-9
    471         * @return this instance
    472         * @throws NullPointerException     if the argument is {@code null}
    473         * @throws IllegalArgumentException if the argument does not have a length of exactly 10
    474         */
    475        public Builder digits(final String digits) {
    476            Objects.requireNonNull(digits, "Digits string cannot be null");
    477            if (digits.length() != DEFAULT_DECIMAL_DIGITS.length()) {
    478                throw new IllegalArgumentException("Digits string must contain exactly " + DEFAULT_DECIMAL_DIGITS.length() + " characters.");
    479            }
    480
    481            this.digits = digits;
    482            return this;
    483        }
    484
    485        /**
    486         * Sets the exponent separator character, i.e., the string placed between the mantissa and the exponent. The default value is {@code "E"}, as in
    487         * {@code "1.2E6"}.
    488         *
    489         * @param exponentSeparator exponent separator string
    490         * @return this instance
    491         * @throws NullPointerException if the argument is {@code null}
    492         */
    493        public Builder exponentSeparator(final String exponentSeparator) {
    494            this.exponentSeparator = Objects.requireNonNull(exponentSeparator, "Exponent separator cannot be null");
    495            return this;
    496        }
    497
    498        /**
    499         * Configures this instance with the given format symbols. The following values are set:
    500         * <ul>
    501         * <li>{@link #digits(String) digit characters}</li>
    502         * <li>{@link #decimalSeparator(char) decimal separator}</li>
    503         * <li>{@link #groupingSeparator(char) thousands grouping separator}</li>
    504         * <li>{@link #minusSign(char) minus sign}</li>
    505         * <li>{@link #exponentSeparator(String) exponent separator}</li>
    506         * <li>{@link #infinity(String) infinity}</li>
    507         * <li>{@link #nan(String) NaN}</li>
    508         * </ul>
    509         * The digit character string is constructed by starting at the configured {@link DecimalFormatSymbols#getZeroDigit() zero digit} and adding the next 9
    510         * consecutive characters.
    511         *
    512         * @param symbols format symbols
    513         * @return this instance
    514         * @throws NullPointerException if the argument is {@code null}
    515         */
    516        public Builder formatSymbols(final DecimalFormatSymbols symbols) {
    517            Objects.requireNonNull(symbols, "Decimal format symbols cannot be null");
    518
    519            return digits(getDigitString(symbols)).decimalSeparator(symbols.getDecimalSeparator()).groupingSeparator(symbols.getGroupingSeparator())
    520                    .minusSign(symbols.getMinusSign()).exponentSeparator(symbols.getExponentSeparator()).infinity(symbols.getInfinity()).nan(symbols.getNaN());
    521        }
    522
    523        /**
    524         * Sets the character used to separate groups of thousands. Default value is {@code ','}.
    525         *
    526         * @param groupingSeparator character used to separate groups of thousands
    527         * @return this instance
    528         * @see #groupThousands(boolean)
    529         */
    530        public Builder groupingSeparator(final char groupingSeparator) {
    531            this.groupingSeparator = groupingSeparator;
    532            return this;
    533        }
    534
    535        /**
    536         * If set to {@code true}, thousands will be grouped with the {@link #groupingSeparator(char) grouping separator}. For example, if set to {@code true},
    537         * the number {@code 1000} could be formatted as {@code "1,000"}. This property only applies to the {@link DoubleFormat#PLAIN PLAIN} format. Default
    538         * value is {@code false}.
    539         *
    540         * @param groupThousands if {@code true}, thousands will be grouped
    541         * @return this instance
    542         * @see #groupingSeparator(char)
    543         */
    544        public Builder groupThousands(final boolean groupThousands) {
    545            this.groupThousands = groupThousands;
    546            return this;
    547        }
    548
    549        /**
    550         * 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
    551         * 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
    552         * is {@code true}.
    553         *
    554         * @param fractionPlaceholder if {@code true}, a zero character is placed in the fraction position when no fractional value is present; if
    555         *                            {@code false}, fractional digits are only included when needed
    556         * @return this instance
    557         */
    558        public Builder includeFractionPlaceholder(final boolean fractionPlaceholder) {
    559            this.fractionPlaceholder = fractionPlaceholder;
    560            return this;
    561        }
    562
    563        /**
    564         * Sets the string used to represent infinity. For negative infinity, this string is prefixed with the {@link #minusSign(char) minus sign}.
    565         *
    566         * @param infinity string used to represent infinity
    567         * @return this instance
    568         * @throws NullPointerException if the argument is {@code null}
    569         */
    570        public Builder infinity(final String infinity) {
    571            this.infinity = Objects.requireNonNull(infinity, "Infinity string cannot be null");
    572            return this;
    573        }
    574
    575        /**
    576         * Sets the maximum number of significant decimal digits used in format results. A value of {@code 0} indicates no limit. The default value is
    577         * {@code 0}.
    578         *
    579         * @param maxPrecision maximum precision
    580         * @return this instance
    581         */
    582        public Builder maxPrecision(final int maxPrecision) {
    583            this.maxPrecision = maxPrecision;
    584            return this;
    585        }
    586
    587        /**
    588         * Sets the minimum decimal exponent for formatted strings. No digits with an absolute value of less than <code>10<sup>minDecimalExponent</sup></code>
    589         * will be included in format results. If the number being formatted does not contain any such digits, then zero is returned. For example, if
    590         * {@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
    591         * {@code 0.001} is formatted, then the result is the zero string.
    592         *
    593         * @param minDecimalExponent minimum decimal exponent
    594         * @return this instance
    595         */
    596        public Builder minDecimalExponent(final int minDecimalExponent) {
    597            this.minDecimalExponent = minDecimalExponent;
    598            return this;
    599        }
    600
    601        /**
    602         * Sets the character used as the minus sign.
    603         *
    604         * @param minusSign character to use as the minus sign
    605         * @return this instance
    606         */
    607        public Builder minusSign(final char minusSign) {
    608            this.minusSign = minusSign;
    609            return this;
    610        }
    611
    612        /**
    613         * Sets the string used to represent {@link Double#NaN}.
    614         *
    615         * @param nan string used to represent {@link Double#NaN}
    616         * @return this instance
    617         * @throws NullPointerException if the argument is {@code null}
    618         */
    619        public Builder nan(final String nan) {
    620            this.nan = Objects.requireNonNull(nan, "NaN string cannot be null");
    621            return this;
    622        }
    623
    624        /**
    625         * Sets the maximum decimal exponent for numbers formatted as plain decimal strings when using the {@link DoubleFormat#MIXED MIXED} format type. If the
    626         * number being formatted has an absolute value less than <code>10<sup>plainFormatMaxDecimalExponent + 1</sup></code> and greater than or equal to
    627         * <code>10<sup>plainFormatMinDecimalExponent</sup></code> after any necessary rounding, then the formatted result will use the
    628         * {@link DoubleFormat#PLAIN PLAIN} format type. Otherwise, {@link DoubleFormat#SCIENTIFIC SCIENTIFIC} format will be used. For example, if this value
    629         * 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"}.
    630         *
    631         * <p>
    632         * The default value is {@code 6}.
    633         *
    634         * <p>
    635         * This value is ignored for formats other than {@link DoubleFormat#MIXED}.
    636         *
    637         * @param plainFormatMaxDecimalExponent maximum decimal exponent for values formatted as plain strings when using the {@link DoubleFormat#MIXED MIXED}
    638         *                                      format type.
    639         * @return this instance
    640         * @see #plainFormatMinDecimalExponent(int)
    641         */
    642        public Builder plainFormatMaxDecimalExponent(final int plainFormatMaxDecimalExponent) {
    643            this.plainFormatMaxDecimalExponent = plainFormatMaxDecimalExponent;
    644            return this;
    645        }
    646
    647        /**
    648         * Sets the minimum decimal exponent for numbers formatted as plain decimal strings when using the {@link DoubleFormat#MIXED MIXED} format type. If the
    649         * number being formatted has an absolute value less than <code>10<sup>plainFormatMaxDecimalExponent + 1</sup></code> and greater than or equal to
    650         * <code>10<sup>plainFormatMinDecimalExponent</sup></code> after any necessary rounding, then the formatted result will use the
    651         * {@link DoubleFormat#PLAIN PLAIN} format type. Otherwise, {@link DoubleFormat#SCIENTIFIC SCIENTIFIC} format will be used. For example, if this value
    652         * 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"}.
    653         *
    654         * <p>
    655         * The default value is {@code -3}.
    656         *
    657         * <p>
    658         * This value is ignored for formats other than {@link DoubleFormat#MIXED}.
    659         *
    660         * @param plainFormatMinDecimalExponent maximum decimal exponent for values formatted as plain strings when using the {@link DoubleFormat#MIXED MIXED}
    661         *                                      format type.
    662         * @return this instance
    663         * @see #plainFormatMinDecimalExponent(int)
    664         */
    665        public Builder plainFormatMinDecimalExponent(final int plainFormatMinDecimalExponent) {
    666            this.plainFormatMinDecimalExponent = plainFormatMinDecimalExponent;
    667            return this;
    668        }
    669    }
    670
    671    /**
    672     * Format class that uses engineering notation for all values.
    673     */
    674    private static final class EngineeringDoubleFormat extends AbstractDoubleFormat {
    675
    676        /**
    677         * Constructs a new instance.
    678         *
    679         * @param builder builder instance containing configuration values
    680         */
    681        EngineeringDoubleFormat(final Builder builder) {
    682            super(builder);
    683        }
    684
    685        /** {@inheritDoc} */
    686        @Override
    687        public String applyFiniteInternal(final ParsedDecimal val) {
    688            return val.toEngineeringString(this);
    689        }
    690    }
    691
    692    /**
    693     * Format class producing results similar to {@link Double#toString()}, with plain decimal notation for small numbers relatively close to zero and
    694     * scientific notation otherwise.
    695     */
    696    private static final class MixedDoubleFormat extends AbstractDoubleFormat {
    697
    698        /** Max decimal exponent for plain format. */
    699        private final int plainMaxExponent;
    700
    701        /** Min decimal exponent for plain format. */
    702        private final int plainMinExponent;
    703
    704        /**
    705         * Constructs a new instance.
    706         *
    707         * @param builder builder instance containing configuration values
    708         */
    709        MixedDoubleFormat(final Builder builder) {
    710            super(builder);
    711
    712            this.plainMaxExponent = builder.plainFormatMaxDecimalExponent;
    713            this.plainMinExponent = builder.plainFormatMinDecimalExponent;
    714        }
    715
    716        /** {@inheritDoc} */
    717        @Override
    718        protected String applyFiniteInternal(final ParsedDecimal val) {
    719            final int sciExp = val.getScientificExponent();
    720            if (sciExp <= plainMaxExponent && sciExp >= plainMinExponent) {
    721                return val.toPlainString(this);
    722            }
    723            return val.toScientificString(this);
    724        }
    725    }
    726
    727    /**
    728     * Format class that produces plain decimal strings that do not use scientific notation.
    729     */
    730    private static final class PlainDoubleFormat extends AbstractDoubleFormat {
    731
    732        /**
    733         * Constructs a new instance.
    734         *
    735         * @param builder builder instance containing configuration values
    736         */
    737        PlainDoubleFormat(final Builder builder) {
    738            super(builder);
    739        }
    740
    741        /**
    742         * {@inheritDoc}
    743         */
    744        @Override
    745        protected String applyFiniteInternal(final ParsedDecimal val) {
    746            return val.toPlainString(this);
    747        }
    748    }
    749
    750    /**
    751     * Format class that uses scientific notation for all values.
    752     */
    753    private static final class ScientificDoubleFormat extends AbstractDoubleFormat {
    754
    755        /**
    756         * Constructs a new instance.
    757         *
    758         * @param builder builder instance containing configuration values
    759         */
    760        ScientificDoubleFormat(final Builder builder) {
    761            super(builder);
    762        }
    763
    764        /** {@inheritDoc} */
    765        @Override
    766        public String applyFiniteInternal(final ParsedDecimal val) {
    767            return val.toScientificString(this);
    768        }
    769    }
    770
    771    /** Function used to construct instances for this format type. */
    772    private final Function<Builder, DoubleFunction<String>> factory;
    773
    774    /**
    775     * Constructs a new instance.
    776     *
    777     * @param factory function used to construct format instances
    778     */
    779    DoubleFormat(final Function<Builder, DoubleFunction<String>> factory) {
    780        this.factory = factory;
    781    }
    782
    783    /**
    784     * Creates a {@link Builder} for building formatter functions for this format type.
    785     *
    786     * @return builder instance
    787     */
    788    public Builder builder() {
    789        return new Builder(factory);
    790    }
    791}