Sort members.
diff --git a/src/main/java/org/apache/commons/text/numbers/DoubleFormat.java b/src/main/java/org/apache/commons/text/numbers/DoubleFormat.java
index 5269a90..1522330 100644
--- a/src/main/java/org/apache/commons/text/numbers/DoubleFormat.java
+++ b/src/main/java/org/apache/commons/text/numbers/DoubleFormat.java
@@ -145,23 +145,166 @@
*/
MIXED(MixedDoubleFormat::new);
- /** Function used to construct instances for this format type. */
- private final Function<Builder, DoubleFunction<String>> factory;
-
/**
- * Constructs a new instance.
- * @param factory function used to construct format instances
+ * Base class for standard double formatting classes.
*/
- DoubleFormat(final Function<Builder, DoubleFunction<String>> factory) {
- this.factory = factory;
- }
+ private abstract static class AbstractDoubleFormat
+ implements DoubleFunction<String>, ParsedDecimal.FormatOptions {
- /**
- * Creates a {@link Builder} for building formatter functions for this format type.
- * @return builder instance
- */
- public Builder builder() {
- return new Builder(factory);
+ /** Maximum precision; 0 indicates no limit. */
+ private final int maxPrecision;
+
+ /** Minimum decimal exponent. */
+ private final int minDecimalExponent;
+
+ /** String representing positive infinity. */
+ private final String positiveInfinity;
+
+ /** String representing negative infinity. */
+ private final String negativeInfinity;
+
+ /** String representing NaN. */
+ private final String nan;
+
+ /** Flag determining if fraction placeholders should be used. */
+ private final boolean fractionPlaceholder;
+
+ /** Flag determining if signed zero strings are allowed. */
+ private final boolean signedZero;
+
+ /** String containing the digits 0-9. */
+ private final char[] digits;
+
+ /** Decimal separator character. */
+ private final char decimalSeparator;
+
+ /** Thousands grouping separator. */
+ private final char groupingSeparator;
+
+ /** Flag indicating if thousands should be grouped. */
+ private final boolean groupThousands;
+
+ /** Minus sign character. */
+ private final char minusSign;
+
+ /** Exponent separator character. */
+ private final char[] exponentSeparatorChars;
+
+ /** Flag indicating if exponent values should always be included, even if zero. */
+ private final boolean alwaysIncludeExponent;
+
+ /**
+ * Constructs a new instance.
+ * @param builder builder instance containing configuration values
+ */
+ AbstractDoubleFormat(final Builder builder) {
+ this.maxPrecision = builder.maxPrecision;
+ this.minDecimalExponent = builder.minDecimalExponent;
+
+ this.positiveInfinity = builder.infinity;
+ this.negativeInfinity = builder.minusSign + builder.infinity;
+ this.nan = builder.nan;
+
+ this.fractionPlaceholder = builder.fractionPlaceholder;
+ this.signedZero = builder.signedZero;
+ this.digits = builder.digits.toCharArray();
+ this.decimalSeparator = builder.decimalSeparator;
+ this.groupingSeparator = builder.groupingSeparator;
+ this.groupThousands = builder.groupThousands;
+ this.minusSign = builder.minusSign;
+ this.exponentSeparatorChars = builder.exponentSeparator.toCharArray();
+ this.alwaysIncludeExponent = builder.alwaysIncludeExponent;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String apply(final double d) {
+ if (Double.isFinite(d)) {
+ return applyFinite(d);
+ } else if (Double.isInfinite(d)) {
+ return d > 0.0
+ ? positiveInfinity
+ : negativeInfinity;
+ }
+ return nan;
+ }
+
+ /**
+ * Returns a formatted string representation of the given finite value.
+ * @param d double value
+ */
+ private String applyFinite(final double d) {
+ final ParsedDecimal n = ParsedDecimal.from(d);
+
+ int roundExponent = Math.max(n.getExponent(), minDecimalExponent);
+ if (maxPrecision > 0) {
+ roundExponent = Math.max(n.getScientificExponent() - maxPrecision + 1, roundExponent);
+ }
+ n.round(roundExponent);
+
+ return applyFiniteInternal(n);
+ }
+
+ /**
+ * Returns a formatted representation of the given rounded decimal value to {@code dst}.
+ * @param val value to format
+ * @return a formatted representation of the given rounded decimal value to {@code dst}.
+ */
+ protected abstract String applyFiniteInternal(ParsedDecimal val);
+
+ /** {@inheritDoc} */
+ @Override
+ public char getDecimalSeparator() {
+ return decimalSeparator;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public char[] getDigits() {
+ return digits;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public char[] getExponentSeparatorChars() {
+ return exponentSeparatorChars;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public char getGroupingSeparator() {
+ return groupingSeparator;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public char getMinusSign() {
+ return minusSign;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isAlwaysIncludeExponent() {
+ return alwaysIncludeExponent;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isGroupThousands() {
+ return groupThousands;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isIncludeFractionPlaceholder() {
+ return fractionPlaceholder;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isSignedZero() {
+ return signedZero;
+ }
}
/**
@@ -236,79 +379,6 @@
}
/**
- * Sets the maximum number of significant decimal digits used in format
- * results. A value of {@code 0} indicates no limit. The default value is {@code 0}.
- * @param maxPrecision maximum precision
- * @return this instance
- */
- public Builder maxPrecision(final int maxPrecision) {
- this.maxPrecision = maxPrecision;
- return this;
- }
-
- /**
- * Sets the minimum decimal exponent for formatted strings. No digits with an
- * absolute value of less than <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;
- }
-
- /**
- * Sets the maximum decimal exponent for numbers formatted as plain decimal strings when
- * using the {@link DoubleFormat#MIXED MIXED} format type. If the number being formatted
- * has an absolute value less than <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;
- }
-
- /**
- * Sets the minimum decimal exponent for numbers formatted as plain decimal strings when
- * using the {@link DoubleFormat#MIXED MIXED} format type. If the number being formatted
- * has an absolute value less than <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;
- }
-
- /**
* Sets the flag determining whether or not the zero string may be returned with the minus
* sign or if it will always be returned in the positive form. For example, if set to {@code true},
* the string {@code "-0.0"} may be returned for some input numbers. If {@code false}, only {@code "0.0"}
@@ -323,6 +393,42 @@
}
/**
+ * Sets the flag indicating if an exponent value should always be included in the
+ * formatted value, even if the exponent value is zero. This property only applies
+ * to formats that use scientific notation, namely
+ * {@link DoubleFormat#SCIENTIFIC SCIENTIFIC},
+ * {@link DoubleFormat#ENGINEERING ENGINEERING}, and
+ * {@link DoubleFormat#MIXED MIXED}. The default value is {@code false}.
+ * @param alwaysIncludeExponent if {@code true}, exponents will always be included in formatted
+ * output even if the exponent value is zero
+ * @return this instance
+ */
+ public Builder alwaysIncludeExponent(final boolean alwaysIncludeExponent) {
+ this.alwaysIncludeExponent = alwaysIncludeExponent;
+ return this;
+ }
+
+ /**
+ * Builds a new double format function.
+ * @return format function
+ */
+ public DoubleFunction<String> build() {
+ return factory.apply(this);
+ }
+
+ /**
+ * Sets the decimal separator character, i.e., the character placed between the
+ * whole number and fractional portions of the formatted strings. The default value
+ * is {@code '.'}.
+ * @param decimalSeparator decimal separator character
+ * @return this instance
+ */
+ public Builder decimalSeparator(final char decimalSeparator) {
+ this.decimalSeparator = decimalSeparator;
+ return this;
+ }
+
+ /**
* Sets the string containing the digit characters 0-9, in that order. The
* default value is the string {@code "0123456789"}.
* @param digits string containing the digit characters 0-9
@@ -342,67 +448,6 @@
}
/**
- * Sets the flag determining whether or not a zero character is added in the fraction position
- * when no fractional value is present. For example, if set to {@code true}, the number {@code 1} would
- * be formatted as {@code "1.0"}. If {@code false}, it would be formatted as {@code "1"}. The default
- * value is {@code true}.
- * @param fractionPlaceholder if {@code true}, a zero character is placed in the fraction position when
- * no fractional value is present; if {@code false}, fractional digits are only included when needed
- * @return this instance
- */
- public Builder includeFractionPlaceholder(final boolean fractionPlaceholder) {
- this.fractionPlaceholder = fractionPlaceholder;
- return this;
- }
-
- /**
- * Sets the character used as the minus sign.
- * @param minusSign character to use as the minus sign
- * @return this instance
- */
- public Builder minusSign(final char minusSign) {
- this.minusSign = minusSign;
- return this;
- }
-
- /**
- * Sets the decimal separator character, i.e., the character placed between the
- * whole number and fractional portions of the formatted strings. The default value
- * is {@code '.'}.
- * @param decimalSeparator decimal separator character
- * @return this instance
- */
- public Builder decimalSeparator(final char decimalSeparator) {
- this.decimalSeparator = decimalSeparator;
- return this;
- }
-
- /**
- * Sets the character used to separate groups of thousands. Default value is {@code ','}.
- * @param groupingSeparator character used to separate groups of thousands
- * @return this instance
- * @see #groupThousands(boolean)
- */
- public Builder groupingSeparator(final char groupingSeparator) {
- this.groupingSeparator = groupingSeparator;
- return this;
- }
-
- /**
- * If set to {@code true}, thousands will be grouped with the
- * {@link #groupingSeparator(char) grouping separator}. For example, if set to {@code true},
- * the number {@code 1000} could be formatted as {@code "1,000"}. This property only applies
- * to the {@link DoubleFormat#PLAIN PLAIN} format. Default value is {@code false}.
- * @param groupThousands if {@code true}, thousands will be grouped
- * @return this instance
- * @see #groupingSeparator(char)
- */
- public Builder groupThousands(final boolean groupThousands) {
- this.groupThousands = groupThousands;
- return this;
- }
-
- /**
* Sets the exponent separator character, i.e., the string placed between
* the mantissa and the exponent. The default value is {@code "E"}, as in
* {@code "1.2E6"}.
@@ -416,45 +461,6 @@
}
/**
- * Sets the flag indicating if an exponent value should always be included in the
- * formatted value, even if the exponent value is zero. This property only applies
- * to formats that use scientific notation, namely
- * {@link DoubleFormat#SCIENTIFIC SCIENTIFIC},
- * {@link DoubleFormat#ENGINEERING ENGINEERING}, and
- * {@link DoubleFormat#MIXED MIXED}. The default value is {@code false}.
- * @param alwaysIncludeExponent if {@code true}, exponents will always be included in formatted
- * output even if the exponent value is zero
- * @return this instance
- */
- public Builder alwaysIncludeExponent(final boolean alwaysIncludeExponent) {
- this.alwaysIncludeExponent = alwaysIncludeExponent;
- return this;
- }
-
- /**
- * Sets the string used to represent infinity. For negative infinity, this string
- * is prefixed with the {@link #minusSign(char) minus sign}.
- * @param infinity string used to represent infinity
- * @return this instance
- * @throws NullPointerException if the argument is {@code null}
- */
- public Builder infinity(final String infinity) {
- this.infinity = Objects.requireNonNull(infinity, "Infinity string cannot be null");
- return this;
- }
-
- /**
- * Sets the string used to represent {@link Double#NaN}.
- * @param nan string used to represent {@link Double#NaN}
- * @return this instance
- * @throws NullPointerException if the argument is {@code null}
- */
- public Builder nan(final String nan) {
- this.nan = Objects.requireNonNull(nan, "NaN string cannot be null");
- return this;
- }
-
- /**
* Configures this instance with the given format symbols. The following values
* are set:
* <ul>
@@ -504,196 +510,168 @@
}
/**
- * Builds a new double format function.
- * @return format function
+ * Sets the character used to separate groups of thousands. Default value is {@code ','}.
+ * @param groupingSeparator character used to separate groups of thousands
+ * @return this instance
+ * @see #groupThousands(boolean)
*/
- public DoubleFunction<String> build() {
- return factory.apply(this);
+ public Builder groupingSeparator(final char groupingSeparator) {
+ this.groupingSeparator = groupingSeparator;
+ return this;
+ }
+
+ /**
+ * If set to {@code true}, thousands will be grouped with the
+ * {@link #groupingSeparator(char) grouping separator}. For example, if set to {@code true},
+ * the number {@code 1000} could be formatted as {@code "1,000"}. This property only applies
+ * to the {@link DoubleFormat#PLAIN PLAIN} format. Default value is {@code false}.
+ * @param groupThousands if {@code true}, thousands will be grouped
+ * @return this instance
+ * @see #groupingSeparator(char)
+ */
+ public Builder groupThousands(final boolean groupThousands) {
+ this.groupThousands = groupThousands;
+ return this;
+ }
+
+ /**
+ * Sets the flag determining whether or not a zero character is added in the fraction position
+ * when no fractional value is present. For example, if set to {@code true}, the number {@code 1} would
+ * be formatted as {@code "1.0"}. If {@code false}, it would be formatted as {@code "1"}. The default
+ * value is {@code true}.
+ * @param fractionPlaceholder if {@code true}, a zero character is placed in the fraction position when
+ * no fractional value is present; if {@code false}, fractional digits are only included when needed
+ * @return this instance
+ */
+ public Builder includeFractionPlaceholder(final boolean fractionPlaceholder) {
+ this.fractionPlaceholder = fractionPlaceholder;
+ return this;
+ }
+
+ /**
+ * Sets the string used to represent infinity. For negative infinity, this string
+ * is prefixed with the {@link #minusSign(char) minus sign}.
+ * @param infinity string used to represent infinity
+ * @return this instance
+ * @throws NullPointerException if the argument is {@code null}
+ */
+ public Builder infinity(final String infinity) {
+ this.infinity = Objects.requireNonNull(infinity, "Infinity string cannot be null");
+ return this;
+ }
+
+ /**
+ * Sets the maximum number of significant decimal digits used in format
+ * results. A value of {@code 0} indicates no limit. The default value is {@code 0}.
+ * @param maxPrecision maximum precision
+ * @return this instance
+ */
+ public Builder maxPrecision(final int maxPrecision) {
+ this.maxPrecision = maxPrecision;
+ return this;
+ }
+
+ /**
+ * Sets the minimum decimal exponent for formatted strings. No digits with an
+ * absolute value of less than <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;
+ }
+
+ /**
+ * Sets the character used as the minus sign.
+ * @param minusSign character to use as the minus sign
+ * @return this instance
+ */
+ public Builder minusSign(final char minusSign) {
+ this.minusSign = minusSign;
+ return this;
+ }
+
+ /**
+ * Sets the string used to represent {@link Double#NaN}.
+ * @param nan string used to represent {@link Double#NaN}
+ * @return this instance
+ * @throws NullPointerException if the argument is {@code null}
+ */
+ public Builder nan(final String nan) {
+ this.nan = Objects.requireNonNull(nan, "NaN string cannot be null");
+ return this;
+ }
+
+ /**
+ * Sets the maximum decimal exponent for numbers formatted as plain decimal strings when
+ * using the {@link DoubleFormat#MIXED MIXED} format type. If the number being formatted
+ * has an absolute value less than <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;
+ }
+
+ /**
+ * Sets the minimum decimal exponent for numbers formatted as plain decimal strings when
+ * using the {@link DoubleFormat#MIXED MIXED} format type. If the number being formatted
+ * has an absolute value less than <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;
}
}
/**
- * Base class for standard double formatting classes.
+ * Format class that uses engineering notation for all values.
*/
- private abstract static class AbstractDoubleFormat
- implements DoubleFunction<String>, ParsedDecimal.FormatOptions {
-
- /** Maximum precision; 0 indicates no limit. */
- private final int maxPrecision;
-
- /** Minimum decimal exponent. */
- private final int minDecimalExponent;
-
- /** String representing positive infinity. */
- private final String positiveInfinity;
-
- /** String representing negative infinity. */
- private final String negativeInfinity;
-
- /** String representing NaN. */
- private final String nan;
-
- /** Flag determining if fraction placeholders should be used. */
- private final boolean fractionPlaceholder;
-
- /** Flag determining if signed zero strings are allowed. */
- private final boolean signedZero;
-
- /** String containing the digits 0-9. */
- private final char[] digits;
-
- /** Decimal separator character. */
- private final char decimalSeparator;
-
- /** Thousands grouping separator. */
- private final char groupingSeparator;
-
- /** Flag indicating if thousands should be grouped. */
- private final boolean groupThousands;
-
- /** Minus sign character. */
- private final char minusSign;
-
- /** Exponent separator character. */
- private final char[] exponentSeparatorChars;
-
- /** Flag indicating if exponent values should always be included, even if zero. */
- private final boolean alwaysIncludeExponent;
+ private static class EngineeringDoubleFormat extends AbstractDoubleFormat {
/**
* Constructs a new instance.
* @param builder builder instance containing configuration values
*/
- AbstractDoubleFormat(final Builder builder) {
- this.maxPrecision = builder.maxPrecision;
- this.minDecimalExponent = builder.minDecimalExponent;
-
- this.positiveInfinity = builder.infinity;
- this.negativeInfinity = builder.minusSign + builder.infinity;
- this.nan = builder.nan;
-
- this.fractionPlaceholder = builder.fractionPlaceholder;
- this.signedZero = builder.signedZero;
- this.digits = builder.digits.toCharArray();
- this.decimalSeparator = builder.decimalSeparator;
- this.groupingSeparator = builder.groupingSeparator;
- this.groupThousands = builder.groupThousands;
- this.minusSign = builder.minusSign;
- this.exponentSeparatorChars = builder.exponentSeparator.toCharArray();
- this.alwaysIncludeExponent = builder.alwaysIncludeExponent;
- }
-
- /** {@inheritDoc} */
- @Override
- public boolean isIncludeFractionPlaceholder() {
- return fractionPlaceholder;
- }
-
- /** {@inheritDoc} */
- @Override
- public boolean isSignedZero() {
- return signedZero;
- }
-
- /** {@inheritDoc} */
- @Override
- public char[] getDigits() {
- return digits;
- }
-
- /** {@inheritDoc} */
- @Override
- public char getDecimalSeparator() {
- return decimalSeparator;
- }
-
- /** {@inheritDoc} */
- @Override
- public char getGroupingSeparator() {
- return groupingSeparator;
- }
-
- /** {@inheritDoc} */
- @Override
- public boolean isGroupThousands() {
- return groupThousands;
- }
-
- /** {@inheritDoc} */
- @Override
- public char getMinusSign() {
- return minusSign;
- }
-
- /** {@inheritDoc} */
- @Override
- public char[] getExponentSeparatorChars() {
- return exponentSeparatorChars;
- }
-
- /** {@inheritDoc} */
- @Override
- public boolean isAlwaysIncludeExponent() {
- return alwaysIncludeExponent;
- }
-
- /** {@inheritDoc} */
- @Override
- public String apply(final double d) {
- if (Double.isFinite(d)) {
- return applyFinite(d);
- } else if (Double.isInfinite(d)) {
- return d > 0.0
- ? positiveInfinity
- : negativeInfinity;
- }
- return nan;
- }
-
- /**
- * Returns a formatted string representation of the given finite value.
- * @param d double value
- */
- private String applyFinite(final double d) {
- final ParsedDecimal n = ParsedDecimal.from(d);
-
- int roundExponent = Math.max(n.getExponent(), minDecimalExponent);
- if (maxPrecision > 0) {
- roundExponent = Math.max(n.getScientificExponent() - maxPrecision + 1, roundExponent);
- }
- n.round(roundExponent);
-
- return applyFiniteInternal(n);
- }
-
- /**
- * Returns a formatted representation of the given rounded decimal value to {@code dst}.
- * @param val value to format
- * @return a formatted representation of the given rounded decimal value to {@code dst}.
- */
- protected abstract String applyFiniteInternal(ParsedDecimal val);
- }
-
- /**
- * Format class that produces plain decimal strings that do not use
- * scientific notation.
- */
- private static class PlainDoubleFormat extends AbstractDoubleFormat {
-
- /**
- * Constructs a new instance.
- * @param builder builder instance containing configuration values
- */
- PlainDoubleFormat(final Builder builder) {
+ EngineeringDoubleFormat(final Builder builder) {
super(builder);
}
- /**
- * {@inheritDoc}
- */
+ /** {@inheritDoc} */
@Override
- protected String applyFiniteInternal(final ParsedDecimal val) {
- return val.toPlainString(this);
+ public String applyFiniteInternal(final ParsedDecimal val) {
+ return val.toEngineeringString(this);
}
}
@@ -733,6 +711,29 @@
}
/**
+ * Format class that produces plain decimal strings that do not use
+ * scientific notation.
+ */
+ private static class PlainDoubleFormat extends AbstractDoubleFormat {
+
+ /**
+ * Constructs a new instance.
+ * @param builder builder instance containing configuration values
+ */
+ PlainDoubleFormat(final Builder builder) {
+ super(builder);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected String applyFiniteInternal(final ParsedDecimal val) {
+ return val.toPlainString(this);
+ }
+ }
+
+ /**
* Format class that uses scientific notation for all values.
*/
private static class ScientificDoubleFormat extends AbstractDoubleFormat {
@@ -752,23 +753,22 @@
}
}
+ /** Function used to construct instances for this format type. */
+ private final Function<Builder, DoubleFunction<String>> factory;
+
/**
- * Format class that uses engineering notation for all values.
+ * Constructs a new instance.
+ * @param factory function used to construct format instances
*/
- private static class EngineeringDoubleFormat extends AbstractDoubleFormat {
+ DoubleFormat(final Function<Builder, DoubleFunction<String>> factory) {
+ this.factory = factory;
+ }
- /**
- * Constructs a new instance.
- * @param builder builder instance containing configuration values
- */
- EngineeringDoubleFormat(final Builder builder) {
- super(builder);
- }
-
- /** {@inheritDoc} */
- @Override
- public String applyFiniteInternal(final ParsedDecimal val) {
- return val.toEngineeringString(this);
- }
+ /**
+ * Creates a {@link Builder} for building formatter functions for this format type.
+ * @return builder instance
+ */
+ public Builder builder() {
+ return new Builder(factory);
}
}
diff --git a/src/main/java/org/apache/commons/text/numbers/ParsedDecimal.java b/src/main/java/org/apache/commons/text/numbers/ParsedDecimal.java
index 05060e6..7581c0d 100644
--- a/src/main/java/org/apache/commons/text/numbers/ParsedDecimal.java
+++ b/src/main/java/org/apache/commons/text/numbers/ParsedDecimal.java
@@ -43,6 +43,50 @@
interface FormatOptions {
/**
+ * Get the decimal separator character.
+ * @return decimal separator character
+ */
+ char getDecimalSeparator();
+
+ /**
+ * Get an array containing the localized digit characters 0-9 in that order.
+ * This string <em>must</em> be non-null and have a length of 10.
+ * @return array containing the digit characters 0-9
+ */
+ char[] getDigits();
+
+ /**
+ * Get the exponent separator as an array of characters.
+ * @return exponent separator as an array of characters
+ */
+ char[] getExponentSeparatorChars();
+
+ /**
+ * Get the character used to separate thousands groupings.
+ * @return character used to separate thousands groupings
+ */
+ char getGroupingSeparator();
+
+ /**
+ * Get the minus sign character.
+ * @return minus sign character
+ */
+ char getMinusSign();
+
+ /**
+ * Return {@code true} if exponent values should always be included in
+ * formatted output, even if the value is zero.
+ * @return {@code true} if exponent values should always be included
+ */
+ boolean isAlwaysIncludeExponent();
+
+ /**
+ * Return {@code true} if thousands should be grouped.
+ * @return {@code true} if thousand should be grouped
+ */
+ boolean isGroupThousands();
+
+ /**
* Return {@code true} if fraction placeholders (e.g., {@code ".0"} in {@code "1.0"})
* should be included.
* @return {@code true} if fraction placeholders should be included
@@ -55,50 +99,6 @@
* @return {@code true} if the minus zero string should be allowed
*/
boolean isSignedZero();
-
- /**
- * Get an array containing the localized digit characters 0-9 in that order.
- * This string <em>must</em> be non-null and have a length of 10.
- * @return array containing the digit characters 0-9
- */
- char[] getDigits();
-
- /**
- * Get the decimal separator character.
- * @return decimal separator character
- */
- char getDecimalSeparator();
-
- /**
- * Get the character used to separate thousands groupings.
- * @return character used to separate thousands groupings
- */
- char getGroupingSeparator();
-
- /**
- * Return {@code true} if thousands should be grouped.
- * @return {@code true} if thousand should be grouped
- */
- boolean isGroupThousands();
-
- /**
- * Get the minus sign character.
- * @return minus sign character
- */
- char getMinusSign();
-
- /**
- * Get the exponent separator as an array of characters.
- * @return exponent separator as an array of characters
- */
- char[] getExponentSeparatorChars();
-
- /**
- * Return {@code true} if exponent values should always be included in
- * formatted output, even if the value is zero.
- * @return {@code true} if exponent values should always be included
- */
- boolean isAlwaysIncludeExponent();
}
/** Minus sign character. */
@@ -125,540 +125,14 @@
/** Number that exponents in engineering format must be a multiple of. */
private static final int ENG_EXPONENT_MOD = 3;
- /** True if the value is negative. */
- final boolean negative;
-
- /** Array containing the significant decimal digits for the value. */
- final int[] digits;
-
- /** Number of digits used in the digits array; not necessarily equal to the length. */
- int digitCount;
-
- /** Exponent for the value. */
- int exponent;
-
- /** Output buffer for use in creating string representations. */
- private char[] outputChars;
-
- /** Output buffer index. */
- private int outputIdx;
-
/**
- * Constructs a new instance from its parts.
- * @param negative {@code true} if the value is negative
- * @param digits array containing significant digits
- * @param digitCount number of digits used from the {@code digits} array
- * @param exponent exponent value
+ * Gets the numeric value of the given digit character. No validation of the
+ * character type is performed.
+ * @param ch digit character
+ * @return numeric value of the digit character, ex: '1' = 1
*/
- private ParsedDecimal(final boolean negative, final int[] digits, final int digitCount,
- final int exponent) {
- this.negative = negative;
- this.digits = digits;
- this.digitCount = digitCount;
- this.exponent = exponent;
- }
-
- /**
- * Gets the exponent value. This exponent produces a floating point value with the
- * correct magnitude when applied to the internal unsigned integer.
- * @return exponent value
- */
- public int getExponent() {
- return exponent;
- }
-
- /**
- * Get sthe exponent that would be used when representing this number in scientific
- * notation (i.e., with a single non-zero digit in front of the decimal point).
- * @return the exponent that would be used when representing this number in scientific
- * notation
- */
- public int getScientificExponent() {
- return digitCount + exponent - 1;
- }
-
- /**
- * Rounds the instance to the given decimal exponent position using
- * {@link java.math.RoundingMode#HALF_EVEN half-even rounding}. For example, a value of {@code -2}
- * will round the instance to the digit at the position 10<sup>-2</sup> (i.e. to the closest multiple of 0.01).
- * @param roundExponent exponent defining the decimal place to round to
- */
- public void round(final int roundExponent) {
- if (roundExponent > exponent) {
- final int max = digitCount + exponent;
-
- if (roundExponent < max) {
- // rounding to a decimal place less than the max; set max precision
- maxPrecision(max - roundExponent);
- } else if (roundExponent == max && shouldRoundUp(0)) {
- // rounding up directly on the max decimal place
- setSingleDigitValue(1, roundExponent);
- } else {
- // change to zero
- setSingleDigitValue(0, 0);
- }
- }
- }
-
- /**
- * Ensures that this instance has <em>at most</em> the given number of significant digits
- * (i.e. precision). If this instance already has a precision less than or equal
- * to the argument, nothing is done. If the given precision requires a reduction in the number
- * of digits, then the value is rounded using {@link java.math.RoundingMode#HALF_EVEN half-even rounding}.
- * @param precision maximum number of significant digits to include
- */
- public void maxPrecision(final int precision) {
- if (precision > 0 && precision < digitCount) {
- if (shouldRoundUp(precision)) {
- roundUp(precision);
- } else {
- truncate(precision);
- }
- }
- }
-
- /**
- * Returns a string representation of this value with no exponent field. Ex:
- * <pre>
- * 10 = "10.0"
- * 1e-6 = "0.000001"
- * 1e11 = "100000000000.0"
- * </pre>
- * @param opts format options
- * @return value in plain format
- */
- public String toPlainString(final FormatOptions opts) {
- final int decimalPos = digitCount + exponent;
- final int fractionZeroCount = decimalPos < 1
- ? Math.abs(decimalPos)
- : 0;
-
- prepareOutput(getPlainStringSize(decimalPos, opts));
-
- final int fractionStartIdx = opts.isGroupThousands()
- ? appendWholeGrouped(decimalPos, opts)
- : appendWhole(decimalPos, opts);
-
- appendFraction(fractionZeroCount, fractionStartIdx, opts);
-
- return outputString();
- }
-
- /**
- * Returns a string representation of this value in scientific notation. Ex:
- * <pre>
- * 0 = "0.0"
- * 10 = "1.0E1"
- * 1e-6 = "1.0E-6"
- * 1e11 = "1.0E11"
- * </pre>
- * @param opts format options
- * @return value in scientific format
- */
- public String toScientificString(final FormatOptions opts) {
- return toScientificString(1, opts);
- }
-
- /**
- * Returns a string representation of this value in engineering notation. This
- * is similar to {@link #toScientificString(FormatOptions) scientific notation}
- * but with the exponent forced to be a multiple of 3, allowing easier alignment with SI prefixes.
- * <pre>
- * 0 = "0.0"
- * 10 = "10.0"
- * 1e-6 = "1.0E-6"
- * 1e11 = "100.0E9"
- * </pre>
- * @param opts format options
- * @return value in engineering format
- */
- public String toEngineeringString(final FormatOptions opts) {
- final int decimalPos = 1 + Math.floorMod(getScientificExponent(), ENG_EXPONENT_MOD);
- return toScientificString(decimalPos, opts);
- }
-
- /**
- * Returns a string representation of the value in scientific notation using the
- * given decimal point position.
- * @param decimalPos decimal position relative to the {@code digits} array; this value
- * is expected to be greater than 0
- * @param opts format options
- * @return value in scientific format
- */
- private String toScientificString(final int decimalPos, final FormatOptions opts) {
- final int targetExponent = digitCount + exponent - decimalPos;
- final int absTargetExponent = Math.abs(targetExponent);
- final boolean includeExponent = shouldIncludeExponent(targetExponent, opts);
- final boolean negativeExponent = targetExponent < 0;
-
- // determine the size of the full formatted string, including the number of
- // characters needed for the exponent digits
- int size = getDigitStringSize(decimalPos, opts);
- int exponentDigitCount = 0;
- if (includeExponent) {
- exponentDigitCount = absTargetExponent > 0
- ? (int) Math.floor(Math.log10(absTargetExponent)) + 1
- : 1;
-
- size += opts.getExponentSeparatorChars().length + exponentDigitCount;
- if (negativeExponent) {
- ++size;
- }
- }
-
- prepareOutput(size);
-
- // append the portion before the exponent field
- final int fractionStartIdx = appendWhole(decimalPos, opts);
- appendFraction(0, fractionStartIdx, opts);
-
- if (includeExponent) {
- // append the exponent field
- append(opts.getExponentSeparatorChars());
-
- if (negativeExponent) {
- append(opts.getMinusSign());
- }
-
- // append the exponent digits themselves; compute the
- // string representation directly and add it to the output
- // buffer to avoid the overhead of Integer.toString()
- final char[] localizedDigits = opts.getDigits();
- int rem = absTargetExponent;
- for (int i = size - 1; i >= outputIdx; --i) {
- outputChars[i] = localizedDigits[rem % DECIMAL_RADIX];
- rem /= DECIMAL_RADIX;
- }
- outputIdx = size;
- }
-
- return outputString();
- }
-
- /**
- * Prepares the output buffer for a string of the given size.
- * @param size buffer size
- */
- private void prepareOutput(final int size) {
- outputChars = new char[size];
- outputIdx = 0;
- }
-
- /**
- * Gets the output buffer as a string.
- * @return output buffer as a string
- */
- private String outputString() {
- final String str = String.valueOf(outputChars);
- outputChars = null;
- return str;
- }
-
- /**
- * Appends the given character to the output buffer.
- * @param ch character to append
- */
- private void append(final char ch) {
- outputChars[outputIdx++] = ch;
- }
-
- /**
- * Appends the given character array directly to the output buffer.
- * @param chars characters to append
- */
- private void append(final char[] chars) {
- for (final char c : chars) {
- append(c);
- }
- }
-
- /**
- * Appends the localized representation of the digit {@code n} to the output buffer.
- * @param n digit to append
- * @param digitChars character array containing localized versions of the digits {@code 0-9}
- * in that order
- */
- private void appendLocalizedDigit(final int n, final char[] digitChars) {
- append(digitChars[n]);
- }
-
- /**
- * Appends the whole number portion of this value to the output buffer. No thousands
- * separators are added.
- * @param wholeCount total number of digits required to the left of the decimal point
- * @param opts format options
- * @return number of digits from {@code digits} appended to the output buffer
- * @see #appendWholeGrouped(int, FormatOptions)
- */
- private int appendWhole(final int wholeCount, final FormatOptions opts) {
- if (shouldIncludeMinus(opts)) {
- append(opts.getMinusSign());
- }
-
- final char[] localizedDigits = opts.getDigits();
- final char localizedZero = localizedDigits[0];
-
- final int significantDigitCount = Math.max(0, Math.min(wholeCount, digitCount));
-
- if (significantDigitCount > 0) {
- int i;
- for (i = 0; i < significantDigitCount; ++i) {
- appendLocalizedDigit(digits[i], localizedDigits);
- }
-
- for (; i < wholeCount; ++i) {
- append(localizedZero);
- }
- } else {
- append(localizedZero);
- }
-
- return significantDigitCount;
- }
-
- /**
- * Appends the whole number portion of this value to the output buffer, adding thousands
- * separators as needed.
- * @param wholeCount total number of digits required to the right of the decimal point
- * @param opts format options
- * @return number of digits from {@code digits} appended to the output buffer
- * @see #appendWhole(int, FormatOptions)
- */
- private int appendWholeGrouped(final int wholeCount, final FormatOptions opts) {
- if (shouldIncludeMinus(opts)) {
- append(opts.getMinusSign());
- }
-
- final char[] localizedDigits = opts.getDigits();
- final char localizedZero = localizedDigits[0];
- final char groupingChar = opts.getGroupingSeparator();
-
- final int appendCount = Math.max(0, Math.min(wholeCount, digitCount));
-
- if (appendCount > 0) {
- int i;
- int pos = wholeCount;
- for (i = 0; i < appendCount; ++i, --pos) {
- appendLocalizedDigit(digits[i], localizedDigits);
- if (requiresGroupingSeparatorAfterPosition(pos)) {
- append(groupingChar);
- }
- }
-
- for (; i < wholeCount; ++i, --pos) {
- append(localizedZero);
- if (requiresGroupingSeparatorAfterPosition(pos)) {
- append(groupingChar);
- }
- }
- } else {
- append(localizedZero);
- }
-
- return appendCount;
- }
-
- /**
- * Returns {@code true} if a grouping separator should be added after the whole digit
- * character at the given position.
- * @param pos whole digit character position, with values starting at 1 and increasing
- * from right to left.
- * @return {@code true} if a grouping separator should be added
- */
- private boolean requiresGroupingSeparatorAfterPosition(final int pos) {
- return pos > 1 && (pos % THOUSANDS_GROUP_SIZE) == 1;
- }
-
- /**
- * Appends the fractional component of the number to the current output buffer.
- * @param zeroCount number of zeros to add after the decimal point and before the
- * first significant digit
- * @param startIdx significant digit start index
- * @param opts format options
- */
- private void appendFraction(final int zeroCount, final int startIdx, final FormatOptions opts) {
- final char[] localizedDigits = opts.getDigits();
- final char localizedZero = localizedDigits[0];
-
- if (startIdx < digitCount) {
- append(opts.getDecimalSeparator());
-
- // add the zero prefix
- for (int i = 0; i < zeroCount; ++i) {
- append(localizedZero);
- }
-
- // add the fraction digits
- for (int i = startIdx; i < digitCount; ++i) {
- appendLocalizedDigit(digits[i], localizedDigits);
- }
- } else if (opts.isIncludeFractionPlaceholder()) {
- append(opts.getDecimalSeparator());
- append(localizedZero);
- }
- }
-
- /**
- * Gets the number of characters required to create a plain format representation
- * of this value.
- * @param decimalPos decimal position relative to the {@code digits} array
- * @param opts format options
- * @return number of characters in the plain string representation of this value,
- * created using the given parameters
- */
- private int getPlainStringSize(final int decimalPos, final FormatOptions opts) {
- int size = getDigitStringSize(decimalPos, opts);
-
- // adjust for groupings if needed
- if (opts.isGroupThousands() && decimalPos > 0) {
- size += (decimalPos - 1) / THOUSANDS_GROUP_SIZE;
- }
-
- return size;
- }
-
- /**
- * Gets the number of characters required for the digit portion of a string representation of
- * this value. This excludes any exponent or thousands groupings characters.
- * @param decimalPos decimal point position relative to the {@code digits} array
- * @param opts format options
- * @return number of characters required for the digit portion of a string representation of
- * this value
- */
- private int getDigitStringSize(final int decimalPos, final FormatOptions opts) {
- int size = digitCount;
- if (shouldIncludeMinus(opts)) {
- ++size;
- }
- if (decimalPos < 1) {
- // no whole component;
- // add decimal point and leading zeros
- size += 2 + Math.abs(decimalPos);
- } else if (decimalPos >= digitCount) {
- // no fraction component;
- // add trailing zeros
- size += decimalPos - digitCount;
- if (opts.isIncludeFractionPlaceholder()) {
- size += 2;
- }
- } else {
- // whole and fraction components;
- // add decimal point
- size += 1;
- }
-
- return size;
- }
-
- /**
- * Returns {@code true} if formatted strings should include the minus sign, considering
- * the value of this instance and the given format options.
- * @param opts format options
- * @return {@code true} if a minus sign should be included in the output
- */
- private boolean shouldIncludeMinus(final FormatOptions opts) {
- return negative && (opts.isSignedZero() || !isZero());
- }
-
- /**
- * Returns {@code true} if a formatted string with the given target exponent should include
- * the exponent field.
- * @param targetExponent exponent of the formatted result
- * @param opts format options
- * @return {@code true} if the formatted string should include the exponent field
- */
- private boolean shouldIncludeExponent(final int targetExponent, final FormatOptions opts) {
- return targetExponent != 0 || opts.isAlwaysIncludeExponent();
- }
-
- /**
- * Returns {@code true} if a rounding operation for the given number of digits should
- * round up.
- * @param count number of digits to round to; must be greater than zero and less
- * than the current number of digits
- * @return {@code true} if a rounding operation for the given number of digits should
- * round up
- */
- private boolean shouldRoundUp(final int count) {
- // Round up in the following cases:
- // 1. The digit after the last digit is greater than 5.
- // 2. The digit after the last digit is 5 and there are additional (non-zero)
- // digits after it.
- // 3. The digit after the last digit is 5, there are no additional digits afterward,
- // and the last digit is odd (half-even rounding).
- final int digitAfterLast = digits[count];
-
- return digitAfterLast > ROUND_CENTER || (digitAfterLast == ROUND_CENTER
- && (count < digitCount - 1 || (digits[count - 1] % 2) != 0));
- }
-
- /**
- * Rounds the value up to the given number of digits.
- * @param count target number of digits; must be greater than zero and
- * less than the current number of digits
- */
- private void roundUp(final int count) {
- int removedDigits = digitCount - count;
- int i;
- for (i = count - 1; i >= 0; --i) {
- final int d = digits[i] + 1;
-
- if (d < DECIMAL_RADIX) {
- // value did not carry over; done adding
- digits[i] = d;
- break;
- }
- // value carried over; the current position is 0
- // which we will ignore by shortening the digit count
- ++removedDigits;
- }
-
- if (i < 0) {
- // all values carried over
- setSingleDigitValue(1, exponent + removedDigits);
- } else {
- // values were updated in-place; just need to update the length
- truncate(digitCount - removedDigits);
- }
- }
-
- /**
- * Returns {@code true} if this value is equal to zero. The sign field is ignored,
- * meaning that this method will return {@code true} for both {@code +0} and {@code -0}.
- * @return {@code true} if the value is equal to zero
- */
- boolean isZero() {
- return digits[0] == 0;
- }
-
- /**
- * Sets the value of this instance to a single digit with the given exponent.
- * The sign of the value is retained.
- * @param digit digit value
- * @param newExponent new exponent value
- */
- private void setSingleDigitValue(final int digit, final int newExponent) {
- digits[0] = digit;
- digitCount = 1;
- exponent = newExponent;
- }
-
- /**
- * Truncates the value to the given number of digits.
- * @param count number of digits; must be greater than zero and less than
- * the current number of digits
- */
- private void truncate(final int count) {
- // trim all trailing zero digits, making sure to leave
- // at least one digit left
- int nonZeroCount = count;
- for (int i = count - 1;
- i > 0 && digits[i] == 0;
- --i) {
- --nonZeroCount;
- }
- exponent += digitCount - nonZeroCount;
- digitCount = nonZeroCount;
+ private static int digitValue(final char ch) {
+ return ch - ZERO_CHAR;
}
/**
@@ -751,13 +225,539 @@
return neg ? -exp : exp;
}
+ /** True if the value is negative. */
+ final boolean negative;
+
+ /** Array containing the significant decimal digits for the value. */
+ final int[] digits;
+
+ /** Number of digits used in the digits array; not necessarily equal to the length. */
+ int digitCount;
+
+ /** Exponent for the value. */
+ int exponent;
+
+ /** Output buffer for use in creating string representations. */
+ private char[] outputChars;
+
+ /** Output buffer index. */
+ private int outputIdx;
+
/**
- * Gets the numeric value of the given digit character. No validation of the
- * character type is performed.
- * @param ch digit character
- * @return numeric value of the digit character, ex: '1' = 1
+ * Constructs a new instance from its parts.
+ * @param negative {@code true} if the value is negative
+ * @param digits array containing significant digits
+ * @param digitCount number of digits used from the {@code digits} array
+ * @param exponent exponent value
*/
- private static int digitValue(final char ch) {
- return ch - ZERO_CHAR;
+ private ParsedDecimal(final boolean negative, final int[] digits, final int digitCount,
+ final int exponent) {
+ this.negative = negative;
+ this.digits = digits;
+ this.digitCount = digitCount;
+ this.exponent = exponent;
+ }
+
+ /**
+ * Appends the given character to the output buffer.
+ * @param ch character to append
+ */
+ private void append(final char ch) {
+ outputChars[outputIdx++] = ch;
+ }
+
+ /**
+ * Appends the given character array directly to the output buffer.
+ * @param chars characters to append
+ */
+ private void append(final char[] chars) {
+ for (final char c : chars) {
+ append(c);
+ }
+ }
+
+ /**
+ * Appends the fractional component of the number to the current output buffer.
+ * @param zeroCount number of zeros to add after the decimal point and before the
+ * first significant digit
+ * @param startIdx significant digit start index
+ * @param opts format options
+ */
+ private void appendFraction(final int zeroCount, final int startIdx, final FormatOptions opts) {
+ final char[] localizedDigits = opts.getDigits();
+ final char localizedZero = localizedDigits[0];
+
+ if (startIdx < digitCount) {
+ append(opts.getDecimalSeparator());
+
+ // add the zero prefix
+ for (int i = 0; i < zeroCount; ++i) {
+ append(localizedZero);
+ }
+
+ // add the fraction digits
+ for (int i = startIdx; i < digitCount; ++i) {
+ appendLocalizedDigit(digits[i], localizedDigits);
+ }
+ } else if (opts.isIncludeFractionPlaceholder()) {
+ append(opts.getDecimalSeparator());
+ append(localizedZero);
+ }
+ }
+
+ /**
+ * Appends the localized representation of the digit {@code n} to the output buffer.
+ * @param n digit to append
+ * @param digitChars character array containing localized versions of the digits {@code 0-9}
+ * in that order
+ */
+ private void appendLocalizedDigit(final int n, final char[] digitChars) {
+ append(digitChars[n]);
+ }
+
+ /**
+ * Appends the whole number portion of this value to the output buffer. No thousands
+ * separators are added.
+ * @param wholeCount total number of digits required to the left of the decimal point
+ * @param opts format options
+ * @return number of digits from {@code digits} appended to the output buffer
+ * @see #appendWholeGrouped(int, FormatOptions)
+ */
+ private int appendWhole(final int wholeCount, final FormatOptions opts) {
+ if (shouldIncludeMinus(opts)) {
+ append(opts.getMinusSign());
+ }
+
+ final char[] localizedDigits = opts.getDigits();
+ final char localizedZero = localizedDigits[0];
+
+ final int significantDigitCount = Math.max(0, Math.min(wholeCount, digitCount));
+
+ if (significantDigitCount > 0) {
+ int i;
+ for (i = 0; i < significantDigitCount; ++i) {
+ appendLocalizedDigit(digits[i], localizedDigits);
+ }
+
+ for (; i < wholeCount; ++i) {
+ append(localizedZero);
+ }
+ } else {
+ append(localizedZero);
+ }
+
+ return significantDigitCount;
+ }
+
+ /**
+ * Appends the whole number portion of this value to the output buffer, adding thousands
+ * separators as needed.
+ * @param wholeCount total number of digits required to the right of the decimal point
+ * @param opts format options
+ * @return number of digits from {@code digits} appended to the output buffer
+ * @see #appendWhole(int, FormatOptions)
+ */
+ private int appendWholeGrouped(final int wholeCount, final FormatOptions opts) {
+ if (shouldIncludeMinus(opts)) {
+ append(opts.getMinusSign());
+ }
+
+ final char[] localizedDigits = opts.getDigits();
+ final char localizedZero = localizedDigits[0];
+ final char groupingChar = opts.getGroupingSeparator();
+
+ final int appendCount = Math.max(0, Math.min(wholeCount, digitCount));
+
+ if (appendCount > 0) {
+ int i;
+ int pos = wholeCount;
+ for (i = 0; i < appendCount; ++i, --pos) {
+ appendLocalizedDigit(digits[i], localizedDigits);
+ if (requiresGroupingSeparatorAfterPosition(pos)) {
+ append(groupingChar);
+ }
+ }
+
+ for (; i < wholeCount; ++i, --pos) {
+ append(localizedZero);
+ if (requiresGroupingSeparatorAfterPosition(pos)) {
+ append(groupingChar);
+ }
+ }
+ } else {
+ append(localizedZero);
+ }
+
+ return appendCount;
+ }
+
+ /**
+ * Gets the number of characters required for the digit portion of a string representation of
+ * this value. This excludes any exponent or thousands groupings characters.
+ * @param decimalPos decimal point position relative to the {@code digits} array
+ * @param opts format options
+ * @return number of characters required for the digit portion of a string representation of
+ * this value
+ */
+ private int getDigitStringSize(final int decimalPos, final FormatOptions opts) {
+ int size = digitCount;
+ if (shouldIncludeMinus(opts)) {
+ ++size;
+ }
+ if (decimalPos < 1) {
+ // no whole component;
+ // add decimal point and leading zeros
+ size += 2 + Math.abs(decimalPos);
+ } else if (decimalPos >= digitCount) {
+ // no fraction component;
+ // add trailing zeros
+ size += decimalPos - digitCount;
+ if (opts.isIncludeFractionPlaceholder()) {
+ size += 2;
+ }
+ } else {
+ // whole and fraction components;
+ // add decimal point
+ size += 1;
+ }
+
+ return size;
+ }
+
+ /**
+ * Gets the exponent value. This exponent produces a floating point value with the
+ * correct magnitude when applied to the internal unsigned integer.
+ * @return exponent value
+ */
+ public int getExponent() {
+ return exponent;
+ }
+
+ /**
+ * Gets the number of characters required to create a plain format representation
+ * of this value.
+ * @param decimalPos decimal position relative to the {@code digits} array
+ * @param opts format options
+ * @return number of characters in the plain string representation of this value,
+ * created using the given parameters
+ */
+ private int getPlainStringSize(final int decimalPos, final FormatOptions opts) {
+ int size = getDigitStringSize(decimalPos, opts);
+
+ // adjust for groupings if needed
+ if (opts.isGroupThousands() && decimalPos > 0) {
+ size += (decimalPos - 1) / THOUSANDS_GROUP_SIZE;
+ }
+
+ return size;
+ }
+
+ /**
+ * Get sthe exponent that would be used when representing this number in scientific
+ * notation (i.e., with a single non-zero digit in front of the decimal point).
+ * @return the exponent that would be used when representing this number in scientific
+ * notation
+ */
+ public int getScientificExponent() {
+ return digitCount + exponent - 1;
+ }
+
+ /**
+ * Returns {@code true} if this value is equal to zero. The sign field is ignored,
+ * meaning that this method will return {@code true} for both {@code +0} and {@code -0}.
+ * @return {@code true} if the value is equal to zero
+ */
+ boolean isZero() {
+ return digits[0] == 0;
+ }
+
+ /**
+ * Ensures that this instance has <em>at most</em> the given number of significant digits
+ * (i.e. precision). If this instance already has a precision less than or equal
+ * to the argument, nothing is done. If the given precision requires a reduction in the number
+ * of digits, then the value is rounded using {@link java.math.RoundingMode#HALF_EVEN half-even rounding}.
+ * @param precision maximum number of significant digits to include
+ */
+ public void maxPrecision(final int precision) {
+ if (precision > 0 && precision < digitCount) {
+ if (shouldRoundUp(precision)) {
+ roundUp(precision);
+ } else {
+ truncate(precision);
+ }
+ }
+ }
+
+ /**
+ * Gets the output buffer as a string.
+ * @return output buffer as a string
+ */
+ private String outputString() {
+ final String str = String.valueOf(outputChars);
+ outputChars = null;
+ return str;
+ }
+
+ /**
+ * Prepares the output buffer for a string of the given size.
+ * @param size buffer size
+ */
+ private void prepareOutput(final int size) {
+ outputChars = new char[size];
+ outputIdx = 0;
+ }
+
+ /**
+ * Returns {@code true} if a grouping separator should be added after the whole digit
+ * character at the given position.
+ * @param pos whole digit character position, with values starting at 1 and increasing
+ * from right to left.
+ * @return {@code true} if a grouping separator should be added
+ */
+ private boolean requiresGroupingSeparatorAfterPosition(final int pos) {
+ return pos > 1 && (pos % THOUSANDS_GROUP_SIZE) == 1;
+ }
+
+ /**
+ * Rounds the instance to the given decimal exponent position using
+ * {@link java.math.RoundingMode#HALF_EVEN half-even rounding}. For example, a value of {@code -2}
+ * will round the instance to the digit at the position 10<sup>-2</sup> (i.e. to the closest multiple of 0.01).
+ * @param roundExponent exponent defining the decimal place to round to
+ */
+ public void round(final int roundExponent) {
+ if (roundExponent > exponent) {
+ final int max = digitCount + exponent;
+
+ if (roundExponent < max) {
+ // rounding to a decimal place less than the max; set max precision
+ maxPrecision(max - roundExponent);
+ } else if (roundExponent == max && shouldRoundUp(0)) {
+ // rounding up directly on the max decimal place
+ setSingleDigitValue(1, roundExponent);
+ } else {
+ // change to zero
+ setSingleDigitValue(0, 0);
+ }
+ }
+ }
+
+ /**
+ * Rounds the value up to the given number of digits.
+ * @param count target number of digits; must be greater than zero and
+ * less than the current number of digits
+ */
+ private void roundUp(final int count) {
+ int removedDigits = digitCount - count;
+ int i;
+ for (i = count - 1; i >= 0; --i) {
+ final int d = digits[i] + 1;
+
+ if (d < DECIMAL_RADIX) {
+ // value did not carry over; done adding
+ digits[i] = d;
+ break;
+ }
+ // value carried over; the current position is 0
+ // which we will ignore by shortening the digit count
+ ++removedDigits;
+ }
+
+ if (i < 0) {
+ // all values carried over
+ setSingleDigitValue(1, exponent + removedDigits);
+ } else {
+ // values were updated in-place; just need to update the length
+ truncate(digitCount - removedDigits);
+ }
+ }
+
+ /**
+ * Sets the value of this instance to a single digit with the given exponent.
+ * The sign of the value is retained.
+ * @param digit digit value
+ * @param newExponent new exponent value
+ */
+ private void setSingleDigitValue(final int digit, final int newExponent) {
+ digits[0] = digit;
+ digitCount = 1;
+ exponent = newExponent;
+ }
+
+ /**
+ * Returns {@code true} if a formatted string with the given target exponent should include
+ * the exponent field.
+ * @param targetExponent exponent of the formatted result
+ * @param opts format options
+ * @return {@code true} if the formatted string should include the exponent field
+ */
+ private boolean shouldIncludeExponent(final int targetExponent, final FormatOptions opts) {
+ return targetExponent != 0 || opts.isAlwaysIncludeExponent();
+ }
+
+ /**
+ * Returns {@code true} if formatted strings should include the minus sign, considering
+ * the value of this instance and the given format options.
+ * @param opts format options
+ * @return {@code true} if a minus sign should be included in the output
+ */
+ private boolean shouldIncludeMinus(final FormatOptions opts) {
+ return negative && (opts.isSignedZero() || !isZero());
+ }
+
+ /**
+ * Returns {@code true} if a rounding operation for the given number of digits should
+ * round up.
+ * @param count number of digits to round to; must be greater than zero and less
+ * than the current number of digits
+ * @return {@code true} if a rounding operation for the given number of digits should
+ * round up
+ */
+ private boolean shouldRoundUp(final int count) {
+ // Round up in the following cases:
+ // 1. The digit after the last digit is greater than 5.
+ // 2. The digit after the last digit is 5 and there are additional (non-zero)
+ // digits after it.
+ // 3. The digit after the last digit is 5, there are no additional digits afterward,
+ // and the last digit is odd (half-even rounding).
+ final int digitAfterLast = digits[count];
+
+ return digitAfterLast > ROUND_CENTER || (digitAfterLast == ROUND_CENTER
+ && (count < digitCount - 1 || (digits[count - 1] % 2) != 0));
+ }
+
+ /**
+ * Returns a string representation of this value in engineering notation. This
+ * is similar to {@link #toScientificString(FormatOptions) scientific notation}
+ * but with the exponent forced to be a multiple of 3, allowing easier alignment with SI prefixes.
+ * <pre>
+ * 0 = "0.0"
+ * 10 = "10.0"
+ * 1e-6 = "1.0E-6"
+ * 1e11 = "100.0E9"
+ * </pre>
+ * @param opts format options
+ * @return value in engineering format
+ */
+ public String toEngineeringString(final FormatOptions opts) {
+ final int decimalPos = 1 + Math.floorMod(getScientificExponent(), ENG_EXPONENT_MOD);
+ return toScientificString(decimalPos, opts);
+ }
+
+ /**
+ * Returns a string representation of this value with no exponent field. Ex:
+ * <pre>
+ * 10 = "10.0"
+ * 1e-6 = "0.000001"
+ * 1e11 = "100000000000.0"
+ * </pre>
+ * @param opts format options
+ * @return value in plain format
+ */
+ public String toPlainString(final FormatOptions opts) {
+ final int decimalPos = digitCount + exponent;
+ final int fractionZeroCount = decimalPos < 1
+ ? Math.abs(decimalPos)
+ : 0;
+
+ prepareOutput(getPlainStringSize(decimalPos, opts));
+
+ final int fractionStartIdx = opts.isGroupThousands()
+ ? appendWholeGrouped(decimalPos, opts)
+ : appendWhole(decimalPos, opts);
+
+ appendFraction(fractionZeroCount, fractionStartIdx, opts);
+
+ return outputString();
+ }
+
+ /**
+ * Returns a string representation of this value in scientific notation. Ex:
+ * <pre>
+ * 0 = "0.0"
+ * 10 = "1.0E1"
+ * 1e-6 = "1.0E-6"
+ * 1e11 = "1.0E11"
+ * </pre>
+ * @param opts format options
+ * @return value in scientific format
+ */
+ public String toScientificString(final FormatOptions opts) {
+ return toScientificString(1, opts);
+ }
+
+ /**
+ * Returns a string representation of the value in scientific notation using the
+ * given decimal point position.
+ * @param decimalPos decimal position relative to the {@code digits} array; this value
+ * is expected to be greater than 0
+ * @param opts format options
+ * @return value in scientific format
+ */
+ private String toScientificString(final int decimalPos, final FormatOptions opts) {
+ final int targetExponent = digitCount + exponent - decimalPos;
+ final int absTargetExponent = Math.abs(targetExponent);
+ final boolean includeExponent = shouldIncludeExponent(targetExponent, opts);
+ final boolean negativeExponent = targetExponent < 0;
+
+ // determine the size of the full formatted string, including the number of
+ // characters needed for the exponent digits
+ int size = getDigitStringSize(decimalPos, opts);
+ int exponentDigitCount = 0;
+ if (includeExponent) {
+ exponentDigitCount = absTargetExponent > 0
+ ? (int) Math.floor(Math.log10(absTargetExponent)) + 1
+ : 1;
+
+ size += opts.getExponentSeparatorChars().length + exponentDigitCount;
+ if (negativeExponent) {
+ ++size;
+ }
+ }
+
+ prepareOutput(size);
+
+ // append the portion before the exponent field
+ final int fractionStartIdx = appendWhole(decimalPos, opts);
+ appendFraction(0, fractionStartIdx, opts);
+
+ if (includeExponent) {
+ // append the exponent field
+ append(opts.getExponentSeparatorChars());
+
+ if (negativeExponent) {
+ append(opts.getMinusSign());
+ }
+
+ // append the exponent digits themselves; compute the
+ // string representation directly and add it to the output
+ // buffer to avoid the overhead of Integer.toString()
+ final char[] localizedDigits = opts.getDigits();
+ int rem = absTargetExponent;
+ for (int i = size - 1; i >= outputIdx; --i) {
+ outputChars[i] = localizedDigits[rem % DECIMAL_RADIX];
+ rem /= DECIMAL_RADIX;
+ }
+ outputIdx = size;
+ }
+
+ return outputString();
+ }
+
+ /**
+ * Truncates the value to the given number of digits.
+ * @param count number of digits; must be greater than zero and less than
+ * the current number of digits
+ */
+ private void truncate(final int count) {
+ // trim all trailing zero digits, making sure to leave
+ // at least one digit left
+ int nonZeroCount = count;
+ for (int i = count - 1;
+ i > 0 && digits[i] == 0;
+ --i) {
+ --nonZeroCount;
+ }
+ exponent += digitCount - nonZeroCount;
+ digitCount = nonZeroCount;
}
}