darkma773r commented on a change in pull request #248: URL: https://github.com/apache/commons-text/pull/248#discussion_r671600875
########## 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; + + /** Construct a new instance that delegates double function construction + * to the given factory object. + * @param factory factory function + */ + private Builder(final Function<Builder, DoubleFunction<String>> factory) { + this.factory = factory; + } + + /** Set 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; + } + + /** Set the minimum decimal exponent for formatted strings. No digits with an + * absolute value of less than <code>10<sup>minDecimalExponent</sup></code> 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; + } + + /** Set 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 <code>10<sup>plainFormatMaxDecimalExponent + 1</sup></code> and + * greater than or equal to <code>10<sup>plainFormatMinDecimalExponent</sup></code> 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"}. + * + * <p>The default value is {@value #DEFAULT_PLAIN_FORMAT_MAX_DECIMAL_EXPONENT}. + * + * <p>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; + } + + /** Set 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 <code>10<sup>plainFormatMaxDecimalExponent + 1</sup></code> and + * greater than or equal to <code>10<sup>plainFormatMinDecimalExponent</sup></code> 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"}. + * + * <p>The default value is {@value #DEFAULT_PLAIN_FORMAT_MIN_DECIMAL_EXPONENT}. + * + * <p>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; + } + + /** Set 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 true, + * the string {@code "-0.0"} may be returned for some input numbers. If 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 true, the zero string may be returned with a preceding minus sign; if 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; + } + + /** Set 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 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"); Review comment: I don't think this needs any changes since `minusSign` and others only accept primitive values. The NPE in the example above would be thrown when computing the arguments to pass to `minusSign`. -- 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]
