• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GENERATED SOURCE. DO NOT MODIFY. */
2 // © 2017 and later: Unicode, Inc. and others.
3 // License & terms of use: http://www.unicode.org/copyright.html#License
4 package ohos.global.icu.number;
5 
6 import java.math.BigDecimal;
7 import java.math.BigInteger;
8 import java.math.MathContext;
9 
10 import ohos.global.icu.impl.number.DecimalQuantity;
11 import ohos.global.icu.impl.number.MultiplierProducer;
12 import ohos.global.icu.impl.number.RoundingUtils;
13 import ohos.global.icu.util.Currency;
14 import ohos.global.icu.util.Currency.CurrencyUsage;
15 
16 /**
17  * A class that defines the rounding precision to be used when formatting numbers in NumberFormatter.
18  *
19  * <p>
20  * To create a Precision, use one of the factory methods.
21  *
22  * @see NumberFormatter
23  * @hide exposed on OHOS
24  */
25 public abstract class Precision {
26 
27     /* package-private final */ MathContext mathContext;
28 
Precision()29     /* package-private */ Precision() {
30         mathContext = RoundingUtils.DEFAULT_MATH_CONTEXT_UNLIMITED;
31     }
32 
33     /**
34      * Show all available digits to full precision.
35      *
36      * <p>
37      * <strong>NOTE:</strong> When formatting a <em>double</em>, this method, along with
38      * {@link #minFraction} and {@link #minSignificantDigits}, will trigger complex algorithm similar to
39      * <em>Dragon4</em> to determine the low-order digits and the number of digits to display based on
40      * the value of the double. If the number of fraction places or significant digits can be bounded,
41      * consider using {@link #maxFraction} or {@link #maxSignificantDigits} instead to maximize performance.
42      * For more information, read the following blog post.
43      *
44      * <p>
45      * http://www.serpentine.com/blog/2011/06/29/here-be-dragons-advances-in-problems-you-didnt-even-know-you-had/
46      *
47      * @return A Precision for chaining or passing to the NumberFormatter precision() setter.
48      * @see NumberFormatter
49      */
unlimited()50     public static Precision unlimited() {
51         return constructInfinite();
52     }
53 
54     /**
55      * Show numbers rounded if necessary to the nearest integer.
56      *
57      * @return A FractionPrecision for chaining or passing to the NumberFormatter precision() setter.
58      * @see NumberFormatter
59      */
integer()60     public static FractionPrecision integer() {
61         return constructFraction(0, 0);
62     }
63 
64     /**
65      * Show numbers rounded if necessary to a certain number of fraction places (numerals after the
66      * decimal separator). Additionally, pad with zeros to ensure that this number of places are always
67      * shown.
68      *
69      * <p>
70      * Example output with minMaxFractionPlaces = 3:
71      *
72      * <p>
73      * 87,650.000<br>
74      * 8,765.000<br>
75      * 876.500<br>
76      * 87.650<br>
77      * 8.765<br>
78      * 0.876<br>
79      * 0.088<br>
80      * 0.009<br>
81      * 0.000 (zero)
82      *
83      * <p>
84      * This method is equivalent to {@link #minMaxFraction} with both arguments equal.
85      *
86      * @param minMaxFractionPlaces
87      *            The minimum and maximum number of numerals to display after the decimal separator
88      *            (rounding if too long or padding with zeros if too short).
89      * @return A FractionPrecision for chaining or passing to the NumberFormatter precision() setter.
90      * @throws IllegalArgumentException if the input number is too big or smaller than 0.
91      * @see NumberFormatter
92      */
fixedFraction(int minMaxFractionPlaces)93     public static FractionPrecision fixedFraction(int minMaxFractionPlaces) {
94         if (minMaxFractionPlaces >= 0 && minMaxFractionPlaces <= RoundingUtils.MAX_INT_FRAC_SIG) {
95             return constructFraction(minMaxFractionPlaces, minMaxFractionPlaces);
96         } else {
97             throw new IllegalArgumentException("Fraction length must be between 0 and "
98                     + RoundingUtils.MAX_INT_FRAC_SIG
99                     + " (inclusive)");
100         }
101     }
102 
103     /**
104      * Always show at least a certain number of fraction places after the decimal separator, padding with
105      * zeros if necessary. Do not perform rounding (display numbers to their full precision).
106      *
107      * <p>
108      * <strong>NOTE:</strong> If you are formatting <em>doubles</em>, see the performance note in
109      * {@link #unlimited}.
110      *
111      * @param minFractionPlaces
112      *            The minimum number of numerals to display after the decimal separator (padding with
113      *            zeros if necessary).
114      * @return A FractionPrecision for chaining or passing to the NumberFormatter precision() setter.
115      * @throws IllegalArgumentException if the input number is too big or smaller than 0.
116      * @see NumberFormatter
117      */
minFraction(int minFractionPlaces)118     public static FractionPrecision minFraction(int minFractionPlaces) {
119         if (minFractionPlaces >= 0 && minFractionPlaces <= RoundingUtils.MAX_INT_FRAC_SIG) {
120             return constructFraction(minFractionPlaces, -1);
121         } else {
122             throw new IllegalArgumentException("Fraction length must be between 0 and "
123                     + RoundingUtils.MAX_INT_FRAC_SIG
124                     + " (inclusive)");
125         }
126     }
127 
128     /**
129      * Show numbers rounded if necessary to a certain number of fraction places (numerals after the
130      * decimal separator). Unlike the other fraction rounding strategies, this strategy does <em>not</em>
131      * pad zeros to the end of the number.
132      *
133      * @param maxFractionPlaces
134      *            The maximum number of numerals to display after the decimal mark (rounding if
135      *            necessary).
136      * @return A FractionPrecision for chaining or passing to the NumberFormatter precision() setter.
137      * @throws IllegalArgumentException if the input number is too big or smaller than 0.
138      * @see NumberFormatter
139      */
maxFraction(int maxFractionPlaces)140     public static FractionPrecision maxFraction(int maxFractionPlaces) {
141         if (maxFractionPlaces >= 0 && maxFractionPlaces <= RoundingUtils.MAX_INT_FRAC_SIG) {
142             return constructFraction(0, maxFractionPlaces);
143         } else {
144             throw new IllegalArgumentException("Fraction length must be between 0 and "
145                     + RoundingUtils.MAX_INT_FRAC_SIG
146                     + " (inclusive)");
147         }
148     }
149 
150     /**
151      * Show numbers rounded if necessary to a certain number of fraction places (numerals after the
152      * decimal separator); in addition, always show at least a certain number of places after the decimal
153      * separator, padding with zeros if necessary.
154      *
155      * @param minFractionPlaces
156      *            The minimum number of numerals to display after the decimal separator (padding with
157      *            zeros if necessary).
158      * @param maxFractionPlaces
159      *            The maximum number of numerals to display after the decimal separator (rounding if
160      *            necessary).
161      * @return A FractionPrecision for chaining or passing to the NumberFormatter precision() setter.
162      * @throws IllegalArgumentException if the input number is too big or smaller than 0.
163      * @see NumberFormatter
164      */
minMaxFraction(int minFractionPlaces, int maxFractionPlaces)165     public static FractionPrecision minMaxFraction(int minFractionPlaces, int maxFractionPlaces) {
166         if (minFractionPlaces >= 0
167                 && maxFractionPlaces <= RoundingUtils.MAX_INT_FRAC_SIG
168                 && minFractionPlaces <= maxFractionPlaces) {
169             return constructFraction(minFractionPlaces, maxFractionPlaces);
170         } else {
171             throw new IllegalArgumentException("Fraction length must be between 0 and "
172                     + RoundingUtils.MAX_INT_FRAC_SIG
173                     + " (inclusive)");
174         }
175     }
176 
177     /**
178      * Show numbers rounded if necessary to a certain number of significant digits or significant
179      * figures. Additionally, pad with zeros to ensure that this number of significant digits/figures are
180      * always shown.
181      *
182      * <p>
183      * This method is equivalent to {@link #minMaxSignificantDigits} with both arguments equal.
184      *
185      * @param minMaxSignificantDigits
186      *            The minimum and maximum number of significant digits to display (rounding if too long
187      *            or padding with zeros if too short).
188      * @return A Precision for chaining or passing to the NumberFormatter precision() setter.
189      * @throws IllegalArgumentException if the input number is too big or smaller than 1.
190      * @see NumberFormatter
191      */
fixedSignificantDigits(int minMaxSignificantDigits)192     public static Precision fixedSignificantDigits(int minMaxSignificantDigits) {
193         if (minMaxSignificantDigits >= 1 && minMaxSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) {
194             return constructSignificant(minMaxSignificantDigits, minMaxSignificantDigits);
195         } else {
196             throw new IllegalArgumentException("Significant digits must be between 1 and "
197                     + RoundingUtils.MAX_INT_FRAC_SIG
198                     + " (inclusive)");
199         }
200     }
201 
202     /**
203      * Always show at least a certain number of significant digits/figures, padding with zeros if
204      * necessary. Do not perform rounding (display numbers to their full precision).
205      *
206      * <p>
207      * <strong>NOTE:</strong> If you are formatting <em>doubles</em>, see the performance note in
208      * {@link #unlimited}.
209      *
210      * @param minSignificantDigits
211      *            The minimum number of significant digits to display (padding with zeros if too short).
212      * @return A Precision for chaining or passing to the NumberFormatter precision() setter.
213      * @throws IllegalArgumentException if the input number is too big or smaller than 1.
214      * @see NumberFormatter
215      */
minSignificantDigits(int minSignificantDigits)216     public static Precision minSignificantDigits(int minSignificantDigits) {
217         if (minSignificantDigits >= 1 && minSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) {
218             return constructSignificant(minSignificantDigits, -1);
219         } else {
220             throw new IllegalArgumentException("Significant digits must be between 1 and "
221                     + RoundingUtils.MAX_INT_FRAC_SIG
222                     + " (inclusive)");
223         }
224     }
225 
226     /**
227      * Show numbers rounded if necessary to a certain number of significant digits/figures.
228      *
229      * @param maxSignificantDigits
230      *            The maximum number of significant digits to display (rounding if too long).
231      * @return A Precision for chaining or passing to the NumberFormatter precision() setter.
232      * @throws IllegalArgumentException if the input number is too big or smaller than 1.
233      * @see NumberFormatter
234      */
maxSignificantDigits(int maxSignificantDigits)235     public static Precision maxSignificantDigits(int maxSignificantDigits) {
236         if (maxSignificantDigits >= 1 && maxSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) {
237             return constructSignificant(1, maxSignificantDigits);
238         } else {
239             throw new IllegalArgumentException("Significant digits must be between 1 and "
240                     + RoundingUtils.MAX_INT_FRAC_SIG
241                     + " (inclusive)");
242         }
243     }
244 
245     /**
246      * Show numbers rounded if necessary to a certain number of significant digits/figures; in addition,
247      * always show at least a certain number of significant digits, padding with zeros if necessary.
248      *
249      * @param minSignificantDigits
250      *            The minimum number of significant digits to display (padding with zeros if necessary).
251      * @param maxSignificantDigits
252      *            The maximum number of significant digits to display (rounding if necessary).
253      * @return A Precision for chaining or passing to the NumberFormatter precision() setter.
254      * @throws IllegalArgumentException if the input number is too big or smaller than 1.
255      * @see NumberFormatter
256      */
minMaxSignificantDigits(int minSignificantDigits, int maxSignificantDigits)257     public static Precision minMaxSignificantDigits(int minSignificantDigits, int maxSignificantDigits) {
258         if (minSignificantDigits >= 1
259                 && maxSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG
260                 && minSignificantDigits <= maxSignificantDigits) {
261             return constructSignificant(minSignificantDigits, maxSignificantDigits);
262         } else {
263             throw new IllegalArgumentException("Significant digits must be between 1 and "
264                     + RoundingUtils.MAX_INT_FRAC_SIG
265                     + " (inclusive)");
266         }
267     }
268 
269     /**
270      * Show numbers rounded if necessary to the closest multiple of a certain rounding increment. For
271      * example, if the rounding increment is 0.5, then round 1.2 to 1 and round 1.3 to 1.5.
272      *
273      * <p>
274      * In order to ensure that numbers are padded to the appropriate number of fraction places, set the
275      * scale on the rounding increment BigDecimal. For example, to round to the nearest 0.5 and always
276      * display 2 numerals after the decimal separator (to display 1.2 as "1.00" and 1.3 as "1.50"), you
277      * can run:
278      *
279      * <pre>
280      * Precision.increment(new BigDecimal("0.50"))
281      * </pre>
282      *
283      * <p>
284      * For more information on the scale of Java BigDecimal, see {@link java.math.BigDecimal#scale()}.
285      *
286      * @param roundingIncrement
287      *            The increment to which to round numbers.
288      * @return A Precision for chaining or passing to the NumberFormatter precision() setter.
289      * @throws IllegalArgumentException if the rounding increment is null or non-positive.
290      * @see NumberFormatter
291      */
increment(BigDecimal roundingIncrement)292     public static Precision increment(BigDecimal roundingIncrement) {
293         if (roundingIncrement != null && roundingIncrement.compareTo(BigDecimal.ZERO) > 0) {
294             return constructIncrement(roundingIncrement);
295         } else {
296             throw new IllegalArgumentException("Rounding increment must be positive and non-null");
297         }
298     }
299 
300     /**
301      * Show numbers rounded and padded according to the rules for the currency unit. The most common
302      * rounding precision settings for currencies include <code>Precision.fixedFraction(2)</code>,
303      * <code>Precision.integer()</code>, and <code>Precision.increment(0.05)</code> for cash transactions
304      * ("nickel rounding").
305      *
306      * <p>
307      * The exact rounding details will be resolved at runtime based on the currency unit specified in the
308      * NumberFormatter chain. To round according to the rules for one currency while displaying the
309      * symbol for another currency, the withCurrency() method can be called on the return value of this
310      * method.
311      *
312      * @param currencyUsage
313      *            Either STANDARD (for digital transactions) or CASH (for transactions where the rounding
314      *            increment may be limited by the available denominations of cash or coins).
315      * @return A CurrencyPrecision for chaining or passing to the NumberFormatter precision() setter.
316      * @throws IllegalArgumentException if currencyUsage is null.
317      * @see NumberFormatter
318      */
currency(CurrencyUsage currencyUsage)319     public static CurrencyPrecision currency(CurrencyUsage currencyUsage) {
320         if (currencyUsage != null) {
321             return constructCurrency(currencyUsage);
322         } else {
323             throw new IllegalArgumentException("CurrencyUsage must be non-null");
324         }
325     }
326 
327     /**
328      * Sets a MathContext to use on this Precision.
329      *
330      * @deprecated This API is ICU internal only.
331      * @hide draft / provisional / internal are hidden on OHOS
332      */
333     @Deprecated
withMode(MathContext mathContext)334     public Precision withMode(MathContext mathContext) {
335         if (this.mathContext.equals(mathContext)) {
336             return this;
337         }
338         Precision other = createCopy();
339         other.mathContext = mathContext;
340         return other;
341     }
342 
343     /** Package-private clone method */
createCopy()344     abstract Precision createCopy();
345 
346     /**
347      * @deprecated ICU 60 This API is ICU internal only.
348      * @hide draft / provisional / internal are hidden on OHOS
349      */
350     @Deprecated
apply(DecimalQuantity value)351     public abstract void apply(DecimalQuantity value);
352 
353     //////////////////////////
354     // PACKAGE-PRIVATE APIS //
355     //////////////////////////
356 
357     static final InfiniteRounderImpl NONE = new InfiniteRounderImpl();
358 
359     static final FractionRounderImpl FIXED_FRAC_0 = new FractionRounderImpl(0, 0);
360     static final FractionRounderImpl FIXED_FRAC_2 = new FractionRounderImpl(2, 2);
361     static final FractionRounderImpl DEFAULT_MAX_FRAC_6 = new FractionRounderImpl(0, 6);
362 
363     static final SignificantRounderImpl FIXED_SIG_2 = new SignificantRounderImpl(2, 2);
364     static final SignificantRounderImpl FIXED_SIG_3 = new SignificantRounderImpl(3, 3);
365     static final SignificantRounderImpl RANGE_SIG_2_3 = new SignificantRounderImpl(2, 3);
366 
367     static final FracSigRounderImpl COMPACT_STRATEGY = new FracSigRounderImpl(0, 0, 2, -1);
368 
369     static final IncrementFiveRounderImpl NICKEL = new IncrementFiveRounderImpl(new BigDecimal("0.05"), 2, 2);
370 
371     static final CurrencyRounderImpl MONETARY_STANDARD = new CurrencyRounderImpl(CurrencyUsage.STANDARD);
372     static final CurrencyRounderImpl MONETARY_CASH = new CurrencyRounderImpl(CurrencyUsage.CASH);
373 
constructInfinite()374     static Precision constructInfinite() {
375         return NONE;
376     }
377 
constructFraction(int minFrac, int maxFrac)378     static FractionPrecision constructFraction(int minFrac, int maxFrac) {
379         if (minFrac == 0 && maxFrac == 0) {
380             return FIXED_FRAC_0;
381         } else if (minFrac == 2 && maxFrac == 2) {
382             return FIXED_FRAC_2;
383         } else if (minFrac == 0 && maxFrac == 6) {
384             return DEFAULT_MAX_FRAC_6;
385         } else {
386             return new FractionRounderImpl(minFrac, maxFrac);
387         }
388     }
389 
390     /** Assumes that minSig <= maxSig. */
constructSignificant(int minSig, int maxSig)391     static Precision constructSignificant(int minSig, int maxSig) {
392         if (minSig == 2 && maxSig == 2) {
393             return FIXED_SIG_2;
394         } else if (minSig == 3 && maxSig == 3) {
395             return FIXED_SIG_3;
396         } else if (minSig == 2 && maxSig == 3) {
397             return RANGE_SIG_2_3;
398         } else {
399             return new SignificantRounderImpl(minSig, maxSig);
400         }
401     }
402 
constructFractionSignificant(FractionPrecision base_, int minSig, int maxSig)403     static Precision constructFractionSignificant(FractionPrecision base_, int minSig, int maxSig) {
404         assert base_ instanceof FractionRounderImpl;
405         FractionRounderImpl base = (FractionRounderImpl) base_;
406         Precision returnValue;
407         if (base.minFrac == 0 && base.maxFrac == 0 && minSig == 2 /* && maxSig == -1 */) {
408             returnValue = COMPACT_STRATEGY;
409         } else {
410             returnValue = new FracSigRounderImpl(base.minFrac, base.maxFrac, minSig, maxSig);
411         }
412         return returnValue.withMode(base.mathContext);
413     }
414 
constructIncrement(BigDecimal increment)415     static Precision constructIncrement(BigDecimal increment) {
416         // NOTE: .equals() is what we want, not .compareTo()
417         if (increment.equals(NICKEL.increment)) {
418             return NICKEL;
419         }
420         // Note: For number formatting, the BigDecimal increment is used for IncrementRounderImpl
421         // but not mIncrementOneRounderImpl or IncrementFiveRounderImpl. However, fIncrement is
422         // used in all three when constructing a skeleton.
423         BigDecimal reduced = increment.stripTrailingZeros();
424         if (reduced.precision() == 1) {
425             int minFrac = increment.scale();
426             int maxFrac = reduced.scale();
427             BigInteger digit = reduced.unscaledValue();
428             if (digit.intValue() == 1) {
429                 return new IncrementOneRounderImpl(increment, minFrac, maxFrac);
430             } else if (digit.intValue() == 5) {
431                 return new IncrementFiveRounderImpl(increment, minFrac, maxFrac);
432             }
433         }
434         return new IncrementRounderImpl(increment);
435     }
436 
constructCurrency(CurrencyUsage usage)437     static CurrencyPrecision constructCurrency(CurrencyUsage usage) {
438         if (usage == CurrencyUsage.STANDARD) {
439             return MONETARY_STANDARD;
440         } else if (usage == CurrencyUsage.CASH) {
441             return MONETARY_CASH;
442         } else {
443             throw new AssertionError();
444         }
445     }
446 
constructFromCurrency(CurrencyPrecision base_, Currency currency)447     static Precision constructFromCurrency(CurrencyPrecision base_, Currency currency) {
448         assert base_ instanceof CurrencyRounderImpl;
449         CurrencyRounderImpl base = (CurrencyRounderImpl) base_;
450         double incrementDouble = currency.getRoundingIncrement(base.usage);
451         Precision returnValue;
452         if (incrementDouble != 0.0) {
453             BigDecimal increment = BigDecimal.valueOf(incrementDouble);
454             returnValue = constructIncrement(increment);
455         } else {
456             int minMaxFrac = currency.getDefaultFractionDigits(base.usage);
457             returnValue = constructFraction(minMaxFrac, minMaxFrac);
458         }
459         return returnValue.withMode(base.mathContext);
460     }
461 
462     /**
463      * Returns a valid working Rounder. If the Rounder is a CurrencyRounder, applies the given currency.
464      * Otherwise, simply passes through the argument.
465      *
466      * @param currency
467      *            A currency object to use in case the input object needs it.
468      * @return A Rounder object ready for use.
469      */
withLocaleData(Currency currency)470     Precision withLocaleData(Currency currency) {
471         if (this instanceof CurrencyPrecision) {
472             return ((CurrencyPrecision) this).withCurrency(currency);
473         } else {
474             return this;
475         }
476     }
477 
478     /**
479      * Rounding endpoint used by Engineering and Compact notation. Chooses the most appropriate
480      * multiplier (magnitude adjustment), applies the adjustment, rounds, and returns the chosen
481      * multiplier.
482      *
483      * <p>
484      * In most cases, this is simple. However, when rounding the number causes it to cross a multiplier
485      * boundary, we need to re-do the rounding. For example, to display 999,999 in Engineering notation
486      * with 2 sigfigs, first you guess the multiplier to be -3. However, then you end up getting 1000E3,
487      * which is not the correct output. You then change your multiplier to be -6, and you get 1.0E6,
488      * which is correct.
489      *
490      * @param input
491      *            The quantity to process.
492      * @param producer
493      *            Function to call to return a multiplier based on a magnitude.
494      * @return The number of orders of magnitude the input was adjusted by this method.
495      */
chooseMultiplierAndApply(DecimalQuantity input, MultiplierProducer producer)496     int chooseMultiplierAndApply(DecimalQuantity input, MultiplierProducer producer) {
497         // Do not call this method with zero, NaN, or infinity.
498         assert !input.isZeroish();
499 
500         // Perform the first attempt at rounding.
501         int magnitude = input.getMagnitude();
502         int multiplier = producer.getMultiplier(magnitude);
503         input.adjustMagnitude(multiplier);
504         apply(input);
505 
506         // If the number rounded to zero, exit.
507         if (input.isZeroish()) {
508             return multiplier;
509         }
510 
511         // If the new magnitude after rounding is the same as it was before rounding, then we are done.
512         // This case applies to most numbers.
513         if (input.getMagnitude() == magnitude + multiplier) {
514             return multiplier;
515         }
516 
517         // If the above case DIDN'T apply, then we have a case like 99.9 -> 100 or 999.9 -> 1000:
518         // The number rounded up to the next magnitude. Check if the multiplier changes; if it doesn't,
519         // we do not need to make any more adjustments.
520         int _multiplier = producer.getMultiplier(magnitude + 1);
521         if (multiplier == _multiplier) {
522             return multiplier;
523         }
524 
525         // We have a case like 999.9 -> 1000, where the correct output is "1K", not "1000".
526         // Fix the magnitude and re-apply the rounding strategy.
527         input.adjustMagnitude(_multiplier - multiplier);
528         apply(input);
529         return _multiplier;
530     }
531 
532     ///////////////
533     // INTERNALS //
534     ///////////////
535 
536     static class InfiniteRounderImpl extends Precision {
537 
InfiniteRounderImpl()538         public InfiniteRounderImpl() {
539         }
540 
541         @Override
apply(DecimalQuantity value)542         public void apply(DecimalQuantity value) {
543             value.roundToInfinity();
544             value.setMinFraction(0);
545         }
546 
547         @Override
createCopy()548         InfiniteRounderImpl createCopy() {
549             InfiniteRounderImpl copy = new InfiniteRounderImpl();
550             copy.mathContext = mathContext;
551             return copy;
552         }
553     }
554 
555     static class FractionRounderImpl extends FractionPrecision {
556         final int minFrac;
557         final int maxFrac;
558 
FractionRounderImpl(int minFrac, int maxFrac)559         public FractionRounderImpl(int minFrac, int maxFrac) {
560             this.minFrac = minFrac;
561             this.maxFrac = maxFrac;
562         }
563 
564         @Override
apply(DecimalQuantity value)565         public void apply(DecimalQuantity value) {
566             value.roundToMagnitude(getRoundingMagnitudeFraction(maxFrac), mathContext);
567             value.setMinFraction(Math.max(0, -getDisplayMagnitudeFraction(minFrac)));
568         }
569 
570         @Override
createCopy()571         FractionRounderImpl createCopy() {
572             FractionRounderImpl copy = new FractionRounderImpl(minFrac, maxFrac);
573             copy.mathContext = mathContext;
574             return copy;
575         }
576     }
577 
578     static class SignificantRounderImpl extends Precision {
579         final int minSig;
580         final int maxSig;
581 
SignificantRounderImpl(int minSig, int maxSig)582         public SignificantRounderImpl(int minSig, int maxSig) {
583             this.minSig = minSig;
584             this.maxSig = maxSig;
585         }
586 
587         @Override
apply(DecimalQuantity value)588         public void apply(DecimalQuantity value) {
589             value.roundToMagnitude(getRoundingMagnitudeSignificant(value, maxSig), mathContext);
590             value.setMinFraction(Math.max(0, -getDisplayMagnitudeSignificant(value, minSig)));
591             // Make sure that digits are displayed on zero.
592             if (value.isZeroish() && minSig > 0) {
593                 value.setMinInteger(1);
594             }
595         }
596 
597         /**
598          * Version of {@link #apply} that obeys minInt constraints. Used for scientific notation
599          * compatibility mode.
600          */
apply(DecimalQuantity quantity, int minInt)601         public void apply(DecimalQuantity quantity, int minInt) {
602             assert quantity.isZeroish();
603             quantity.setMinFraction(minSig - minInt);
604         }
605 
606         @Override
createCopy()607         SignificantRounderImpl createCopy() {
608             SignificantRounderImpl copy = new SignificantRounderImpl(minSig, maxSig);
609             copy.mathContext = mathContext;
610             return copy;
611         }
612     }
613 
614     static class FracSigRounderImpl extends Precision {
615         final int minFrac;
616         final int maxFrac;
617         final int minSig;
618         final int maxSig;
619 
FracSigRounderImpl(int minFrac, int maxFrac, int minSig, int maxSig)620         public FracSigRounderImpl(int minFrac, int maxFrac, int minSig, int maxSig) {
621             this.minFrac = minFrac;
622             this.maxFrac = maxFrac;
623             this.minSig = minSig;
624             this.maxSig = maxSig;
625         }
626 
627         @Override
apply(DecimalQuantity value)628         public void apply(DecimalQuantity value) {
629             int displayMag = getDisplayMagnitudeFraction(minFrac);
630             int roundingMag = getRoundingMagnitudeFraction(maxFrac);
631             if (minSig == -1) {
632                 // Max Sig override
633                 int candidate = getRoundingMagnitudeSignificant(value, maxSig);
634                 roundingMag = Math.max(roundingMag, candidate);
635             } else {
636                 // Min Sig override
637                 int candidate = getDisplayMagnitudeSignificant(value, minSig);
638                 roundingMag = Math.min(roundingMag, candidate);
639             }
640             value.roundToMagnitude(roundingMag, mathContext);
641             value.setMinFraction(Math.max(0, -displayMag));
642         }
643 
644         @Override
createCopy()645         FracSigRounderImpl createCopy() {
646             FracSigRounderImpl copy = new FracSigRounderImpl(minFrac, maxFrac, minSig, maxSig);
647             copy.mathContext = mathContext;
648             return copy;
649         }
650     }
651 
652     /**
653      * Used for strange increments like 3.14.
654      */
655     static class IncrementRounderImpl extends Precision {
656         final BigDecimal increment;
657 
IncrementRounderImpl(BigDecimal increment)658         public IncrementRounderImpl(BigDecimal increment) {
659             this.increment = increment;
660         }
661 
662         @Override
apply(DecimalQuantity value)663         public void apply(DecimalQuantity value) {
664             value.roundToIncrement(increment, mathContext);
665             value.setMinFraction(increment.scale());
666         }
667 
668         @Override
createCopy()669         IncrementRounderImpl createCopy() {
670             IncrementRounderImpl copy = new IncrementRounderImpl(increment);
671             copy.mathContext = mathContext;
672             return copy;
673         }
674     }
675 
676     /**
677      * Used for increments with 1 as the only digit. This is different than fraction
678      * rounding because it supports having additional trailing zeros. For example, this
679      * class is used to round with the increment 0.010.
680      */
681     static class IncrementOneRounderImpl extends IncrementRounderImpl {
682         final int minFrac;
683         final int maxFrac;
684 
IncrementOneRounderImpl(BigDecimal increment, int minFrac, int maxFrac)685         public IncrementOneRounderImpl(BigDecimal increment, int minFrac, int maxFrac) {
686             super(increment);
687             this.minFrac = minFrac;
688             this.maxFrac = maxFrac;
689         }
690 
691         @Override
apply(DecimalQuantity value)692         public void apply(DecimalQuantity value) {
693             value.roundToMagnitude(-maxFrac, mathContext);
694             value.setMinFraction(minFrac);
695         }
696 
697         @Override
createCopy()698         IncrementOneRounderImpl createCopy() {
699             IncrementOneRounderImpl copy = new IncrementOneRounderImpl(increment, minFrac, maxFrac);
700             copy.mathContext = mathContext;
701             return copy;
702         }
703     }
704 
705     /**
706      * Used for increments with 5 as the only digit (nickel rounding).
707      */
708     static class IncrementFiveRounderImpl extends IncrementRounderImpl {
709         final int minFrac;
710         final int maxFrac;
711 
IncrementFiveRounderImpl(BigDecimal increment, int minFrac, int maxFrac)712         public IncrementFiveRounderImpl(BigDecimal increment, int minFrac, int maxFrac) {
713             super(increment);
714             this.minFrac = minFrac;
715             this.maxFrac = maxFrac;
716         }
717 
718         @Override
apply(DecimalQuantity value)719         public void apply(DecimalQuantity value) {
720             value.roundToNickel(-maxFrac, mathContext);
721             value.setMinFraction(minFrac);
722         }
723 
724         @Override
createCopy()725         IncrementFiveRounderImpl createCopy() {
726             IncrementFiveRounderImpl copy = new IncrementFiveRounderImpl(increment, minFrac, maxFrac);
727             copy.mathContext = mathContext;
728             return copy;
729         }
730     }
731 
732     static class CurrencyRounderImpl extends CurrencyPrecision {
733         final CurrencyUsage usage;
734 
CurrencyRounderImpl(CurrencyUsage usage)735         public CurrencyRounderImpl(CurrencyUsage usage) {
736             this.usage = usage;
737         }
738 
739         @Override
apply(DecimalQuantity value)740         public void apply(DecimalQuantity value) {
741             // Call .withCurrency() before .apply()!
742             throw new AssertionError();
743         }
744 
745         @Override
createCopy()746         CurrencyRounderImpl createCopy() {
747             CurrencyRounderImpl copy = new CurrencyRounderImpl(usage);
748             copy.mathContext = mathContext;
749             return copy;
750         }
751     }
752 
getRoundingMagnitudeFraction(int maxFrac)753     private static int getRoundingMagnitudeFraction(int maxFrac) {
754         if (maxFrac == -1) {
755             return Integer.MIN_VALUE;
756         }
757         return -maxFrac;
758     }
759 
getRoundingMagnitudeSignificant(DecimalQuantity value, int maxSig)760     private static int getRoundingMagnitudeSignificant(DecimalQuantity value, int maxSig) {
761         if (maxSig == -1) {
762             return Integer.MIN_VALUE;
763         }
764         int magnitude = value.isZeroish() ? 0 : value.getMagnitude();
765         return magnitude - maxSig + 1;
766     }
767 
getDisplayMagnitudeFraction(int minFrac)768     private static int getDisplayMagnitudeFraction(int minFrac) {
769         if (minFrac == 0) {
770             return Integer.MAX_VALUE;
771         }
772         return -minFrac;
773     }
774 
getDisplayMagnitudeSignificant(DecimalQuantity value, int minSig)775     private static int getDisplayMagnitudeSignificant(DecimalQuantity value, int minSig) {
776         int magnitude = value.isZeroish() ? 0 : value.getMagnitude();
777         return magnitude - minSig + 1;
778     }
779 }
780