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