All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ibm.icu.number.Precision Maven / Gradle / Ivy

Go to download

International Component for Unicode for Java (ICU4J) is a mature, widely used Java library providing Unicode and Globalization support

There is a newer version: 76.1
Show newest version
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
package com.ibm.icu.number;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;

import com.ibm.icu.impl.number.DecimalQuantity;
import com.ibm.icu.impl.number.MultiplierProducer;
import com.ibm.icu.impl.number.RoundingUtils;
import com.ibm.icu.number.NumberFormatter.RoundingPriority;
import com.ibm.icu.number.NumberFormatter.TrailingZeroDisplay;
import com.ibm.icu.text.PluralRules.Operand;
import com.ibm.icu.util.Currency;
import com.ibm.icu.util.Currency.CurrencyUsage;

/**
 * A class that defines the rounding precision to be used when formatting numbers in NumberFormatter.
 *
 * 

* To create a Precision, use one of the factory methods. * * @stable ICU 62 * @see NumberFormatter */ public abstract class Precision { /* package-private final */ MathContext mathContext; /* package-private final */ TrailingZeroDisplay trailingZeroDisplay; /* package-private */ Precision() { mathContext = RoundingUtils.DEFAULT_MATH_CONTEXT_UNLIMITED; } /** * Show all available digits to full precision. * *

* NOTE: When formatting a double, this method, along with * {@link #minFraction} and {@link #minSignificantDigits}, will trigger complex algorithm similar to * Dragon4 to determine the low-order digits and the number of digits to display based on * the value of the double. If the number of fraction places or significant digits can be bounded, * consider using {@link #maxFraction} or {@link #maxSignificantDigits} instead to maximize performance. * For more information, read the following blog post. * *

* http://www.serpentine.com/blog/2011/06/29/here-be-dragons-advances-in-problems-you-didnt-even-know-you-had/ * * @return A Precision for chaining or passing to the NumberFormatter precision() setter. * @stable ICU 60 * @see NumberFormatter */ public static Precision unlimited() { return constructInfinite(); } /** * Show numbers rounded if necessary to the nearest integer. * * @return A FractionPrecision for chaining or passing to the NumberFormatter precision() setter. * @stable ICU 60 * @see NumberFormatter */ public static FractionPrecision integer() { return constructFraction(0, 0); } /** * Show numbers rounded if necessary to a certain number of fraction places (numerals after the * decimal separator). Additionally, pad with zeros to ensure that this number of places are always * shown. * *

* Example output with minMaxFractionPlaces = 3: * *

* 87,650.000
* 8,765.000
* 876.500
* 87.650
* 8.765
* 0.876
* 0.088
* 0.009
* 0.000 (zero) * *

* This method is equivalent to {@link #minMaxFraction} with both arguments equal. * * @param minMaxFractionPlaces * The minimum and maximum number of numerals to display after the decimal separator * (rounding if too long or padding with zeros if too short). * @return A FractionPrecision for chaining or passing to the NumberFormatter precision() setter. * @throws IllegalArgumentException if the input number is too big or smaller than 0. * @stable ICU 60 * @see NumberFormatter */ public static FractionPrecision fixedFraction(int minMaxFractionPlaces) { if (minMaxFractionPlaces >= 0 && minMaxFractionPlaces <= RoundingUtils.MAX_INT_FRAC_SIG) { return constructFraction(minMaxFractionPlaces, minMaxFractionPlaces); } else { throw new IllegalArgumentException("Fraction length must be between 0 and " + RoundingUtils.MAX_INT_FRAC_SIG + " (inclusive)"); } } /** * Always show at least a certain number of fraction places after the decimal separator, padding with * zeros if necessary. Do not perform rounding (display numbers to their full precision). * *

* NOTE: If you are formatting doubles, see the performance note in * {@link #unlimited}. * * @param minFractionPlaces * The minimum number of numerals to display after the decimal separator (padding with * zeros if necessary). * @return A FractionPrecision for chaining or passing to the NumberFormatter precision() setter. * @throws IllegalArgumentException if the input number is too big or smaller than 0. * @stable ICU 60 * @see NumberFormatter */ public static FractionPrecision minFraction(int minFractionPlaces) { if (minFractionPlaces >= 0 && minFractionPlaces <= RoundingUtils.MAX_INT_FRAC_SIG) { return constructFraction(minFractionPlaces, -1); } else { throw new IllegalArgumentException("Fraction length must be between 0 and " + RoundingUtils.MAX_INT_FRAC_SIG + " (inclusive)"); } } /** * Show numbers rounded if necessary to a certain number of fraction places (numerals after the * decimal separator). Unlike the other fraction rounding strategies, this strategy does not * pad zeros to the end of the number. * * @param maxFractionPlaces * The maximum number of numerals to display after the decimal mark (rounding if * necessary). * @return A FractionPrecision for chaining or passing to the NumberFormatter precision() setter. * @throws IllegalArgumentException if the input number is too big or smaller than 0. * @stable ICU 60 * @see NumberFormatter */ public static FractionPrecision maxFraction(int maxFractionPlaces) { if (maxFractionPlaces >= 0 && maxFractionPlaces <= RoundingUtils.MAX_INT_FRAC_SIG) { return constructFraction(0, maxFractionPlaces); } else { throw new IllegalArgumentException("Fraction length must be between 0 and " + RoundingUtils.MAX_INT_FRAC_SIG + " (inclusive)"); } } /** * Show numbers rounded if necessary to a certain number of fraction places (numerals after the * decimal separator); in addition, always show at least a certain number of places after the decimal * separator, padding with zeros if necessary. * * @param minFractionPlaces * The minimum number of numerals to display after the decimal separator (padding with * zeros if necessary). * @param maxFractionPlaces * The maximum number of numerals to display after the decimal separator (rounding if * necessary). * @return A FractionPrecision for chaining or passing to the NumberFormatter precision() setter. * @throws IllegalArgumentException if the input number is too big or smaller than 0. * @stable ICU 60 * @see NumberFormatter */ public static FractionPrecision minMaxFraction(int minFractionPlaces, int maxFractionPlaces) { if (minFractionPlaces >= 0 && maxFractionPlaces <= RoundingUtils.MAX_INT_FRAC_SIG && minFractionPlaces <= maxFractionPlaces) { return constructFraction(minFractionPlaces, maxFractionPlaces); } else { throw new IllegalArgumentException("Fraction length must be between 0 and " + RoundingUtils.MAX_INT_FRAC_SIG + " (inclusive)"); } } /** * Show numbers rounded if necessary to a certain number of significant digits or significant * figures. Additionally, pad with zeros to ensure that this number of significant digits/figures are * always shown. * *

* This method is equivalent to {@link #minMaxSignificantDigits} with both arguments equal. * * @param minMaxSignificantDigits * The minimum and maximum number of significant digits to display (rounding if too long * or padding with zeros if too short). * @return A Precision for chaining or passing to the NumberFormatter precision() setter. * @throws IllegalArgumentException if the input number is too big or smaller than 1. * @stable ICU 62 * @see NumberFormatter */ public static Precision fixedSignificantDigits(int minMaxSignificantDigits) { if (minMaxSignificantDigits >= 1 && minMaxSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) { return constructSignificant(minMaxSignificantDigits, minMaxSignificantDigits); } else { throw new IllegalArgumentException("Significant digits must be between 1 and " + RoundingUtils.MAX_INT_FRAC_SIG + " (inclusive)"); } } /** * Always show at least a certain number of significant digits/figures, padding with zeros if * necessary. Do not perform rounding (display numbers to their full precision). * *

* NOTE: If you are formatting doubles, see the performance note in * {@link #unlimited}. * * @param minSignificantDigits * The minimum number of significant digits to display (padding with zeros if too short). * @return A Precision for chaining or passing to the NumberFormatter precision() setter. * @throws IllegalArgumentException if the input number is too big or smaller than 1. * @stable ICU 62 * @see NumberFormatter */ public static Precision minSignificantDigits(int minSignificantDigits) { if (minSignificantDigits >= 1 && minSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) { return constructSignificant(minSignificantDigits, -1); } else { throw new IllegalArgumentException("Significant digits must be between 1 and " + RoundingUtils.MAX_INT_FRAC_SIG + " (inclusive)"); } } /** * Show numbers rounded if necessary to a certain number of significant digits/figures. * * @param maxSignificantDigits * The maximum number of significant digits to display (rounding if too long). * @return A Precision for chaining or passing to the NumberFormatter precision() setter. * @throws IllegalArgumentException if the input number is too big or smaller than 1. * @stable ICU 62 * @see NumberFormatter */ public static Precision maxSignificantDigits(int maxSignificantDigits) { if (maxSignificantDigits >= 1 && maxSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) { return constructSignificant(1, maxSignificantDigits); } else { throw new IllegalArgumentException("Significant digits must be between 1 and " + RoundingUtils.MAX_INT_FRAC_SIG + " (inclusive)"); } } /** * Show numbers rounded if necessary to a certain number of significant digits/figures; in addition, * always show at least a certain number of significant digits, padding with zeros if necessary. * * @param minSignificantDigits * The minimum number of significant digits to display (padding with zeros if necessary). * @param maxSignificantDigits * The maximum number of significant digits to display (rounding if necessary). * @return A Precision for chaining or passing to the NumberFormatter precision() setter. * @throws IllegalArgumentException if the input number is too big or smaller than 1. * @stable ICU 62 * @see NumberFormatter */ public static Precision minMaxSignificantDigits(int minSignificantDigits, int maxSignificantDigits) { if (minSignificantDigits >= 1 && maxSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG && minSignificantDigits <= maxSignificantDigits) { return constructSignificant(minSignificantDigits, maxSignificantDigits); } else { throw new IllegalArgumentException("Significant digits must be between 1 and " + RoundingUtils.MAX_INT_FRAC_SIG + " (inclusive)"); } } /** * Show numbers rounded if necessary to the closest multiple of a certain rounding increment. For * example, if the rounding increment is 0.5, then round 1.2 to 1 and round 1.3 to 1.5. * *

* In order to ensure that numbers are padded to the appropriate number of fraction places, set the * scale on the rounding increment BigDecimal. For example, to round to the nearest 0.5 and always * display 2 numerals after the decimal separator (to display 1.2 as "1.00" and 1.3 as "1.50"), you * can run: * *

     * Precision.increment(new BigDecimal("0.50"))
     * 
* *

* For more information on the scale of Java BigDecimal, see {@link java.math.BigDecimal#scale()}. * * @param roundingIncrement * The increment to which to round numbers. * @return A Precision for chaining or passing to the NumberFormatter precision() setter. * @throws IllegalArgumentException if the rounding increment is null or non-positive. * @stable ICU 60 * @see NumberFormatter */ public static Precision increment(BigDecimal roundingIncrement) { if (roundingIncrement != null && roundingIncrement.compareTo(BigDecimal.ZERO) > 0) { return constructIncrement(roundingIncrement); } else { throw new IllegalArgumentException("Rounding increment must be positive and non-null"); } } /** * Show numbers rounded and padded according to the rules for the currency unit. The most common * rounding precision settings for currencies include Precision.fixedFraction(2), * Precision.integer(), and Precision.increment(0.05) for cash transactions * ("nickel rounding"). * *

* The exact rounding details will be resolved at runtime based on the currency unit specified in the * NumberFormatter chain. To round according to the rules for one currency while displaying the * symbol for another currency, the withCurrency() method can be called on the return value of this * method. * * @param currencyUsage * Either STANDARD (for digital transactions) or CASH (for transactions where the rounding * increment may be limited by the available denominations of cash or coins). * @return A CurrencyPrecision for chaining or passing to the NumberFormatter precision() setter. * @throws IllegalArgumentException if currencyUsage is null. * @stable ICU 60 * @see NumberFormatter */ public static CurrencyPrecision currency(CurrencyUsage currencyUsage) { if (currencyUsage != null) { return constructCurrency(currencyUsage); } else { throw new IllegalArgumentException("CurrencyUsage must be non-null"); } } /** * Configure how trailing zeros are displayed on numbers. For example, to hide trailing zeros * when the number is an integer, use HIDE_IF_WHOLE. * * @param trailingZeroDisplay Option to configure the display of trailing zeros. * @draft ICU 69 */ public Precision trailingZeroDisplay(TrailingZeroDisplay trailingZeroDisplay) { Precision result = this.createCopy(); result.trailingZeroDisplay = trailingZeroDisplay; return result; } /** * Sets a MathContext to use on this Precision. * * @internal * @deprecated This API is ICU internal only. */ @Deprecated public Precision withMode(MathContext mathContext) { if (this.mathContext.equals(mathContext)) { return this; } Precision other = createCopy(); other.mathContext = mathContext; return other; } /** Package-private clone method */ abstract Precision createCopy(); /** * @internal * @deprecated ICU 60 This API is ICU internal only. */ @Deprecated public abstract void apply(DecimalQuantity value); ////////////////////////// // PACKAGE-PRIVATE APIS // ////////////////////////// /** * @internal * @deprecated ICU internal only. */ @Deprecated public static final BogusRounder BOGUS_PRECISION = new BogusRounder(); static final InfiniteRounderImpl NONE = new InfiniteRounderImpl(); static final FractionRounderImpl FIXED_FRAC_0 = new FractionRounderImpl(0, 0); static final FractionRounderImpl FIXED_FRAC_2 = new FractionRounderImpl(2, 2); static final FractionRounderImpl DEFAULT_MAX_FRAC_6 = new FractionRounderImpl(0, 6); static final SignificantRounderImpl FIXED_SIG_2 = new SignificantRounderImpl(2, 2); static final SignificantRounderImpl FIXED_SIG_3 = new SignificantRounderImpl(3, 3); static final SignificantRounderImpl RANGE_SIG_2_3 = new SignificantRounderImpl(2, 3); static final FracSigRounderImpl COMPACT_STRATEGY = new FracSigRounderImpl(0, 0, 1, 2, RoundingPriority.RELAXED); static final IncrementFiveRounderImpl NICKEL = new IncrementFiveRounderImpl(new BigDecimal("0.05"), 2, 2); static final CurrencyRounderImpl MONETARY_STANDARD = new CurrencyRounderImpl(CurrencyUsage.STANDARD); static final CurrencyRounderImpl MONETARY_CASH = new CurrencyRounderImpl(CurrencyUsage.CASH); static Precision constructInfinite() { return NONE; } static FractionPrecision constructFraction(int minFrac, int maxFrac) { if (minFrac == 0 && maxFrac == 0) { return FIXED_FRAC_0; } else if (minFrac == 2 && maxFrac == 2) { return FIXED_FRAC_2; } else if (minFrac == 0 && maxFrac == 6) { return DEFAULT_MAX_FRAC_6; } else { return new FractionRounderImpl(minFrac, maxFrac); } } /** Assumes that minSig <= maxSig. */ static Precision constructSignificant(int minSig, int maxSig) { if (minSig == 2 && maxSig == 2) { return FIXED_SIG_2; } else if (minSig == 3 && maxSig == 3) { return FIXED_SIG_3; } else if (minSig == 2 && maxSig == 3) { return RANGE_SIG_2_3; } else { return new SignificantRounderImpl(minSig, maxSig); } } static Precision constructFractionSignificant( FractionPrecision base_, int minSig, int maxSig, RoundingPriority priority) { assert base_ instanceof FractionRounderImpl; FractionRounderImpl base = (FractionRounderImpl) base_; Precision returnValue; if (base.minFrac == 0 && base.maxFrac == 0 && minSig == 1 && maxSig == 2 && priority == RoundingPriority.RELAXED) { returnValue = COMPACT_STRATEGY; } else { returnValue = new FracSigRounderImpl(base.minFrac, base.maxFrac, minSig, maxSig, priority); } return returnValue.withMode(base.mathContext); } static Precision constructIncrement(BigDecimal increment) { // NOTE: .equals() is what we want, not .compareTo() if (increment.equals(NICKEL.increment)) { return NICKEL; } // Note: For number formatting, the BigDecimal increment is used for IncrementRounderImpl // but not mIncrementOneRounderImpl or IncrementFiveRounderImpl. However, fIncrement is // used in all three when constructing a skeleton. BigDecimal reduced = increment.stripTrailingZeros(); if (reduced.precision() == 1) { int minFrac = increment.scale(); int maxFrac = reduced.scale(); BigInteger digit = reduced.unscaledValue(); if (digit.intValue() == 1) { return new IncrementOneRounderImpl(increment, minFrac, maxFrac); } else if (digit.intValue() == 5) { return new IncrementFiveRounderImpl(increment, minFrac, maxFrac); } } return new IncrementRounderImpl(increment); } static CurrencyPrecision constructCurrency(CurrencyUsage usage) { if (usage == CurrencyUsage.STANDARD) { return MONETARY_STANDARD; } else if (usage == CurrencyUsage.CASH) { return MONETARY_CASH; } else { throw new AssertionError(); } } static Precision constructFromCurrency(CurrencyPrecision base_, Currency currency) { assert base_ instanceof CurrencyRounderImpl; CurrencyRounderImpl base = (CurrencyRounderImpl) base_; double incrementDouble = currency.getRoundingIncrement(base.usage); Precision returnValue; if (incrementDouble != 0.0) { BigDecimal increment = BigDecimal.valueOf(incrementDouble); returnValue = constructIncrement(increment); } else { int minMaxFrac = currency.getDefaultFractionDigits(base.usage); returnValue = constructFraction(minMaxFrac, minMaxFrac); } return returnValue.withMode(base.mathContext); } /** * Returns a valid working Rounder. If the Rounder is a CurrencyRounder, applies the given currency. * Otherwise, simply passes through the argument. * * @param currency * A currency object to use in case the input object needs it. * @return A Rounder object ready for use. */ Precision withLocaleData(Currency currency) { if (this instanceof CurrencyPrecision) { return ((CurrencyPrecision) this).withCurrency(currency); } else { return this; } } /** * Rounding endpoint used by Engineering and Compact notation. Chooses the most appropriate * multiplier (magnitude adjustment), applies the adjustment, rounds, and returns the chosen * multiplier. * *

* In most cases, this is simple. However, when rounding the number causes it to cross a multiplier * boundary, we need to re-do the rounding. For example, to display 999,999 in Engineering notation * with 2 sigfigs, first you guess the multiplier to be -3. However, then you end up getting 1000E3, * which is not the correct output. You then change your multiplier to be -6, and you get 1.0E6, * which is correct. * * @param input * The quantity to process. * @param producer * Function to call to return a multiplier based on a magnitude. * @return The number of orders of magnitude the input was adjusted by this method. */ int chooseMultiplierAndApply(DecimalQuantity input, MultiplierProducer producer) { // Do not call this method with zero, NaN, or infinity. assert !input.isZeroish(); // Perform the first attempt at rounding. int magnitude = input.getMagnitude(); int multiplier = producer.getMultiplier(magnitude); input.adjustMagnitude(multiplier); apply(input); // If the number rounded to zero, exit. if (input.isZeroish()) { return multiplier; } // If the new magnitude after rounding is the same as it was before rounding, then we are done. // This case applies to most numbers. if (input.getMagnitude() == magnitude + multiplier) { return multiplier; } // If the above case DIDN'T apply, then we have a case like 99.9 -> 100 or 999.9 -> 1000: // The number rounded up to the next magnitude. Check if the multiplier changes; if it doesn't, // we do not need to make any more adjustments. int _multiplier = producer.getMultiplier(magnitude + 1); if (multiplier == _multiplier) { return multiplier; } // We have a case like 999.9 -> 1000, where the correct output is "1K", not "1000". // Fix the magnitude and re-apply the rounding strategy. input.adjustMagnitude(_multiplier - multiplier); apply(input); return _multiplier; } /////////////// // INTERNALS // /////////////// /** * An BogusRounder's MathContext into precision. * * @internal * @deprecated This API is ICU internal only. */ @Deprecated public static class BogusRounder extends Precision { /** * Default constructor. * @internal * @deprecated This API is ICU internal only. */ @Deprecated public BogusRounder() { } /** * {@inheritDoc} * @internal * @deprecated This API is ICU internal only. */ @Override @Deprecated public void apply(DecimalQuantity value) { throw new AssertionError("BogusRounder must not be applied"); } @Override BogusRounder createCopy() { BogusRounder copy = new BogusRounder(); copy.mathContext = mathContext; return copy; } /** * Copies the BogusRounder's MathContext into precision. * * @internal * @deprecated This API is ICU internal only. */ @Deprecated public Precision into(Precision precision) { Precision copy = precision.createCopy(); copy.mathContext = mathContext; return copy; } } static class InfiniteRounderImpl extends Precision { public InfiniteRounderImpl() { } @Override public void apply(DecimalQuantity value) { value.roundToInfinity(); setResolvedMinFraction(value, 0); } @Override InfiniteRounderImpl createCopy() { InfiniteRounderImpl copy = new InfiniteRounderImpl(); copy.mathContext = mathContext; return copy; } } static class FractionRounderImpl extends FractionPrecision { final int minFrac; final int maxFrac; public FractionRounderImpl(int minFrac, int maxFrac) { this.minFrac = minFrac; this.maxFrac = maxFrac; } @Override public void apply(DecimalQuantity value) { value.roundToMagnitude(getRoundingMagnitudeFraction(maxFrac), mathContext); setResolvedMinFraction(value, Math.max(0, -getDisplayMagnitudeFraction(minFrac))); } @Override FractionRounderImpl createCopy() { FractionRounderImpl copy = new FractionRounderImpl(minFrac, maxFrac); copy.mathContext = mathContext; return copy; } } static class SignificantRounderImpl extends Precision { final int minSig; final int maxSig; public SignificantRounderImpl(int minSig, int maxSig) { this.minSig = minSig; this.maxSig = maxSig; } @Override public void apply(DecimalQuantity value) { value.roundToMagnitude(getRoundingMagnitudeSignificant(value, maxSig), mathContext); setResolvedMinFraction(value, Math.max(0, -getDisplayMagnitudeSignificant(value, minSig))); // Make sure that digits are displayed on zero. if (value.isZeroish() && minSig > 0) { value.setMinInteger(1); } } /** * Version of {@link #apply} that obeys minInt constraints. Used for scientific notation * compatibility mode. */ public void apply(DecimalQuantity quantity, int minInt) { assert quantity.isZeroish(); setResolvedMinFraction(quantity, minSig - minInt); } @Override SignificantRounderImpl createCopy() { SignificantRounderImpl copy = new SignificantRounderImpl(minSig, maxSig); copy.mathContext = mathContext; return copy; } } static class FracSigRounderImpl extends Precision { final int minFrac; final int maxFrac; final int minSig; final int maxSig; final RoundingPriority priority; public FracSigRounderImpl(int minFrac, int maxFrac, int minSig, int maxSig, RoundingPriority priority) { this.minFrac = minFrac; this.maxFrac = maxFrac; this.minSig = minSig; this.maxSig = maxSig; this.priority = priority; } @Override public void apply(DecimalQuantity value) { int roundingMag1 = getRoundingMagnitudeFraction(maxFrac); int roundingMag2 = getRoundingMagnitudeSignificant(value, maxSig); int roundingMag; if (priority == RoundingPriority.RELAXED) { roundingMag = Math.min(roundingMag1, roundingMag2); } else { roundingMag = Math.max(roundingMag1, roundingMag2); } value.roundToMagnitude(roundingMag, mathContext); int displayMag1 = getDisplayMagnitudeFraction(minFrac); int displayMag2 = getDisplayMagnitudeSignificant(value, minSig); int displayMag = Math.min(displayMag1, displayMag2); setResolvedMinFraction(value, Math.max(0, -displayMag)); } @Override FracSigRounderImpl createCopy() { FracSigRounderImpl copy = new FracSigRounderImpl(minFrac, maxFrac, minSig, maxSig, priority); copy.mathContext = mathContext; return copy; } } /** * Used for strange increments like 3.14. */ static class IncrementRounderImpl extends Precision { final BigDecimal increment; public IncrementRounderImpl(BigDecimal increment) { this.increment = increment; } @Override public void apply(DecimalQuantity value) { value.roundToIncrement(increment, mathContext); setResolvedMinFraction(value, increment.scale()); } @Override IncrementRounderImpl createCopy() { IncrementRounderImpl copy = new IncrementRounderImpl(increment); copy.mathContext = mathContext; return copy; } } /** * Used for increments with 1 as the only digit. This is different than fraction * rounding because it supports having additional trailing zeros. For example, this * class is used to round with the increment 0.010. */ static class IncrementOneRounderImpl extends IncrementRounderImpl { final int minFrac; final int maxFrac; public IncrementOneRounderImpl(BigDecimal increment, int minFrac, int maxFrac) { super(increment); this.minFrac = minFrac; this.maxFrac = maxFrac; } @Override public void apply(DecimalQuantity value) { value.roundToMagnitude(-maxFrac, mathContext); setResolvedMinFraction(value, minFrac); } @Override IncrementOneRounderImpl createCopy() { IncrementOneRounderImpl copy = new IncrementOneRounderImpl(increment, minFrac, maxFrac); copy.mathContext = mathContext; return copy; } } /** * Used for increments with 5 as the only digit (nickel rounding). */ static class IncrementFiveRounderImpl extends IncrementRounderImpl { final int minFrac; final int maxFrac; public IncrementFiveRounderImpl(BigDecimal increment, int minFrac, int maxFrac) { super(increment); this.minFrac = minFrac; this.maxFrac = maxFrac; } @Override public void apply(DecimalQuantity value) { value.roundToNickel(-maxFrac, mathContext); setResolvedMinFraction(value, minFrac); } @Override IncrementFiveRounderImpl createCopy() { IncrementFiveRounderImpl copy = new IncrementFiveRounderImpl(increment, minFrac, maxFrac); copy.mathContext = mathContext; return copy; } } static class CurrencyRounderImpl extends CurrencyPrecision { final CurrencyUsage usage; public CurrencyRounderImpl(CurrencyUsage usage) { this.usage = usage; } @Override public void apply(DecimalQuantity value) { // Call .withCurrency() before .apply()! throw new AssertionError(); } @Override CurrencyRounderImpl createCopy() { CurrencyRounderImpl copy = new CurrencyRounderImpl(usage); copy.mathContext = mathContext; return copy; } } private static int getRoundingMagnitudeFraction(int maxFrac) { if (maxFrac == -1) { return Integer.MIN_VALUE; } return -maxFrac; } private static int getRoundingMagnitudeSignificant(DecimalQuantity value, int maxSig) { if (maxSig == -1) { return Integer.MIN_VALUE; } int magnitude = value.isZeroish() ? 0 : value.getMagnitude(); return magnitude - maxSig + 1; } private static int getDisplayMagnitudeFraction(int minFrac) { if (minFrac == 0) { return Integer.MAX_VALUE; } return -minFrac; } void setResolvedMinFraction(DecimalQuantity value, int resolvedMinFraction) { if (trailingZeroDisplay == null || trailingZeroDisplay == TrailingZeroDisplay.AUTO || // PLURAL_OPERAND_T returns fraction digits as an integer value.getPluralOperand(Operand.t) != 0) { value.setMinFraction(resolvedMinFraction); } } private static int getDisplayMagnitudeSignificant(DecimalQuantity value, int minSig) { // Question: Is it useful to look at trailingZeroDisplay here? int magnitude = value.isZeroish() ? 0 : value.getMagnitude(); return magnitude - minSig + 1; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy