darkma773r commented on a change in pull request #248: URL: https://github.com/apache/commons-text/pull/248#discussion_r671691789
########## File path: src/main/java/org/apache/commons/text/numbers/DoubleFormat.java ########## @@ -0,0 +1,736 @@ +/* + * 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}. + * + * <p><strong>Comparison with DecimalFormat</strong> + * <p>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}. + * + * <p><strong>Examples</strong> + * <pre> + * // 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() + * </pre> + * + * <p><strong>Implementation Notes</strong> + * <p>{@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. + */ +public enum DoubleFormat { + + /** Number format without exponents. + * Ex: + * <pre> + * 0.0 + * 12.401 + * 100000.0 + * 1450000000.0 + * 0.0000000000123 + * </pre> + */ + PLAIN(PlainDoubleFormat::new), + + /** Number format that uses exponents and contains a single digit + * to the left of the decimal point. + * Ex: + * <pre> + * 0.0 + * 1.2401E1 + * 1.0E5 + * 1.45E9 + * 1.23E-11 + * </pre> + */ + 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: + * <pre> + * 0.0 + * 12.401 + * 100.0E3 + * 1.45E9 + * 12.3E-12 + * </pre> + */ + 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: + * <pre> + * 0.0 + * 12.401 + * 100000.0 + * 1.45E9 + * 1.23E-11 + * </pre> + */ + MIXED(MixedDoubleFormat::new); + + /** Function used to construct instances for this format type. */ + private final Function<Builder, DoubleFunction<String>> factory; + + /** Construct a new instance. + * @param factory function used to construct format instances + */ + DoubleFormat(final Function<Builder, DoubleFunction<String>> factory) { + this.factory = factory; + } + + /** Return a {@link Builder} for constructing formatter functions for this format type. + * @return builder instance + */ + public Builder builder() { + return new Builder(factory); + } + + /** Class for constructing 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<Builder, DoubleFunction<String>> 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 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; Review comment: I've gone back and forth on this. On one hand, having builder options that only apply to certain formats seems confusing. Users must read the docs in order to ensure that an option will apply to their situation. On the other hand, having separate builder types for each format would make the API considerably more complicated. Plus, with a single builder API, users can separate their configuration from the actual format used. For example, ```java private static DoubleFunction<String> createFormatter(DoubleFormat formatType) { return formatType.builder() .maxPrecision(6) .alwaysIncludeExponent(true) // may or may not apply .build(); } ``` So, I'm still leaning toward the current implementation, where the builder type contains a superset of all options across all format types. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
