• 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
4 package android.icu.number;
5 
6 import java.text.Format.Field;
7 
8 import android.icu.impl.FormattedStringBuilder;
9 import android.icu.impl.number.ConstantAffixModifier;
10 import android.icu.impl.number.DecimalQuantity;
11 import android.icu.impl.number.MicroProps;
12 import android.icu.impl.number.MicroPropsGenerator;
13 import android.icu.impl.number.Modifier;
14 import android.icu.impl.number.MultiplierProducer;
15 import android.icu.impl.number.RoundingUtils;
16 import android.icu.number.NumberFormatter.SignDisplay;
17 import android.icu.number.Precision.SignificantRounderImpl;
18 import android.icu.text.DecimalFormatSymbols;
19 import android.icu.text.NumberFormat;
20 
21 /**
22  * A class that defines the scientific notation style to be used when formatting numbers in
23  * NumberFormatter.
24  *
25  * <p>
26  * To create a ScientificNotation, use one of the factory methods in {@link Notation}.
27  *
28  * @see NumberFormatter
29  */
30 public class ScientificNotation extends Notation {
31 
32     int engineeringInterval;
33     boolean requireMinInt;
34     int minExponentDigits;
35     SignDisplay exponentSignDisplay;
36 
ScientificNotation( int engineeringInterval, boolean requireMinInt, int minExponentDigits, SignDisplay exponentSignDisplay)37     /* package-private */ ScientificNotation(
38             int engineeringInterval,
39             boolean requireMinInt,
40             int minExponentDigits,
41             SignDisplay exponentSignDisplay) {
42         this.engineeringInterval = engineeringInterval;
43         this.requireMinInt = requireMinInt;
44         this.minExponentDigits = minExponentDigits;
45         this.exponentSignDisplay = exponentSignDisplay;
46     }
47 
48     /**
49      * Sets the minimum number of digits to show in the exponent of scientific notation, padding with
50      * zeros if necessary. Useful for fixed-width display.
51      *
52      * <p>
53      * For example, with minExponentDigits=2, the number 123 will be printed as "1.23E02" in
54      * <em>en-US</em> instead of the default "1.23E2".
55      *
56      * @param minExponentDigits
57      *            The minimum number of digits to show in the exponent.
58      * @return A ScientificNotation, for chaining.
59      * @throws IllegalArgumentException if minExponentDigits is too big or smaller than 1
60      * @see NumberFormatter
61      */
withMinExponentDigits(int minExponentDigits)62     public ScientificNotation withMinExponentDigits(int minExponentDigits) {
63         if (minExponentDigits >= 1 && minExponentDigits <= RoundingUtils.MAX_INT_FRAC_SIG) {
64             ScientificNotation other = createCopy();
65             other.minExponentDigits = minExponentDigits;
66             return other;
67         } else {
68             throw new IllegalArgumentException("Integer digits must be between 1 and "
69                     + RoundingUtils.MAX_INT_FRAC_SIG
70                     + " (inclusive)");
71         }
72     }
73 
74     /**
75      * Sets whether to show the sign on positive and negative exponents in scientific notation. The
76      * default is AUTO, showing the minus sign but not the plus sign.
77      *
78      * <p>
79      * For example, with exponentSignDisplay=ALWAYS, the number 123 will be printed as "1.23E+2" in
80      * <em>en-US</em> instead of the default "1.23E2".
81      *
82      * @param exponentSignDisplay
83      *            The strategy for displaying the sign in the exponent.
84      * @return A ScientificNotation, for chaining.
85      * @see NumberFormatter
86      */
withExponentSignDisplay(SignDisplay exponentSignDisplay)87     public ScientificNotation withExponentSignDisplay(SignDisplay exponentSignDisplay) {
88         ScientificNotation other = createCopy();
89         other.exponentSignDisplay = exponentSignDisplay;
90         return other;
91     }
92 
93     /** Package-private clone method */
createCopy()94     ScientificNotation createCopy() {
95         return new ScientificNotation(
96             engineeringInterval,
97             requireMinInt,
98             minExponentDigits,
99             exponentSignDisplay
100         );
101     }
102 
withLocaleData( DecimalFormatSymbols symbols, boolean build, MicroPropsGenerator parent)103     /* package-private */ MicroPropsGenerator withLocaleData(
104             DecimalFormatSymbols symbols,
105             boolean build,
106             MicroPropsGenerator parent) {
107         return new ScientificHandler(this, symbols, build, parent);
108     }
109 
110     // NOTE: The object lifecycle of ScientificModifier and ScientificHandler differ greatly in Java and
111     // C++.
112     //
113     // During formatting, we need to provide an object with state (the exponent) as the inner modifier.
114     //
115     // In Java, where the priority is put on reducing object creations, the unsafe code path re-uses the
116     // ScientificHandler as a ScientificModifier, and the safe code path pre-computes 25
117     // ScientificModifier
118     // instances. This scheme reduces the number of object creations by 1 in both safe and unsafe.
119     //
120     // In C++, MicroProps provides a pre-allocated ScientificModifier, and ScientificHandler simply
121     // populates
122     // the state (the exponent) into that ScientificModifier. There is no difference between safe and
123     // unsafe.
124 
125     private static class ScientificHandler implements MicroPropsGenerator, MultiplierProducer, Modifier {
126 
127         final ScientificNotation notation;
128         final DecimalFormatSymbols symbols;
129         final ScientificModifier[] precomputedMods;
130         final MicroPropsGenerator parent;
131         /* unsafe */ int exponent;
132 
ScientificHandler( ScientificNotation notation, DecimalFormatSymbols symbols, boolean safe, MicroPropsGenerator parent)133         private ScientificHandler(
134                 ScientificNotation notation,
135                 DecimalFormatSymbols symbols,
136                 boolean safe,
137                 MicroPropsGenerator parent) {
138             this.notation = notation;
139             this.symbols = symbols;
140             this.parent = parent;
141 
142             if (safe) {
143                 // Pre-build the modifiers for exponents -12 through 12
144                 precomputedMods = new ScientificModifier[25];
145                 for (int i = -12; i <= 12; i++) {
146                     precomputedMods[i + 12] = new ScientificModifier(i, this);
147                 }
148             } else {
149                 precomputedMods = null;
150             }
151         }
152 
153         @Override
processQuantity(DecimalQuantity quantity)154         public MicroProps processQuantity(DecimalQuantity quantity) {
155             MicroProps micros = parent.processQuantity(quantity);
156             assert micros.rounder != null;
157 
158             // Do not apply scientific notation to special doubles
159             if (quantity.isInfinite() || quantity.isNaN()) {
160                 micros.modInner = ConstantAffixModifier.EMPTY;
161                 return micros;
162             }
163 
164             // Treat zero as if it had magnitude 0
165             int exponent;
166             if (quantity.isZeroish()) {
167                 if (notation.requireMinInt && micros.rounder instanceof SignificantRounderImpl) {
168                     // Show "00.000E0" on pattern "00.000E0"
169                     ((SignificantRounderImpl) micros.rounder).apply(quantity,
170                             notation.engineeringInterval);
171                     exponent = 0;
172                 } else {
173                     micros.rounder.apply(quantity);
174                     exponent = 0;
175                 }
176             } else {
177                 exponent = -micros.rounder.chooseMultiplierAndApply(quantity, this);
178             }
179 
180             // Add the Modifier for the scientific format.
181             if (precomputedMods != null && exponent >= -12 && exponent <= 12) {
182                 // Safe code path A
183                 micros.modInner = precomputedMods[exponent + 12];
184             } else if (precomputedMods != null) {
185                 // Safe code path B
186                 micros.modInner = new ScientificModifier(exponent, this);
187             } else {
188                 // Unsafe code path: mutates the object and re-uses it as a Modifier!
189                 this.exponent = exponent;
190                 micros.modInner = this;
191             }
192 
193             // Change the exponent only after we select appropriate plural form
194             // for formatting purposes so that we preserve expected formatted
195             // string behavior.
196             quantity.adjustExponent(exponent);
197 
198             // We already performed rounding. Do not perform it again.
199             micros.rounder = null;
200 
201             return micros;
202         }
203 
204         @Override
getMultiplier(int magnitude)205         public int getMultiplier(int magnitude) {
206             int interval = notation.engineeringInterval;
207             int digitsShown;
208             if (notation.requireMinInt) {
209                 // For patterns like "000.00E0" and ".00E0"
210                 digitsShown = interval;
211             } else if (interval <= 1) {
212                 // For patterns like "0.00E0" and "@@@E0"
213                 digitsShown = 1;
214             } else {
215                 // For patterns like "##0.00"
216                 digitsShown = ((magnitude % interval + interval) % interval) + 1;
217             }
218             return digitsShown - magnitude - 1;
219         }
220 
221         @Override
getPrefixLength()222         public int getPrefixLength() {
223             // TODO: Localized exponent separator location.
224             return 0;
225         }
226 
227         @Override
getCodePointCount()228         public int getCodePointCount() {
229             // NOTE: This method is only called one place, NumberRangeFormatterImpl.
230             // The call site only cares about != 0 and != 1.
231             // Return a very large value so that if this method is used elsewhere, we should notice.
232             return 999;
233         }
234 
235         @Override
isStrong()236         public boolean isStrong() {
237             // Scientific is always strong
238             return true;
239         }
240 
241         @Override
containsField(Field field)242         public boolean containsField(Field field) {
243             // This method is not currently used. (unsafe path not used in range formatting)
244             assert false;
245             return false;
246         }
247 
248         @Override
getParameters()249         public Parameters getParameters() {
250             // This method is not currently used.
251             assert false;
252             return null;
253         }
254 
255         @Override
strictEquals(Modifier other)256         public boolean strictEquals(Modifier other) {
257             // This method is not currently used. (unsafe path not used in range formatting)
258             assert false;
259             return false;
260         }
261 
262         @Override
apply(FormattedStringBuilder output, int leftIndex, int rightIndex)263         public int apply(FormattedStringBuilder output, int leftIndex, int rightIndex) {
264             return doApply(exponent, output, rightIndex);
265         }
266 
doApply(int exponent, FormattedStringBuilder output, int rightIndex)267         private int doApply(int exponent, FormattedStringBuilder output, int rightIndex) {
268             // FIXME: Localized exponent separator location.
269             int i = rightIndex;
270             // Append the exponent separator and sign
271             i += output.insert(i, symbols.getExponentSeparator(), NumberFormat.Field.EXPONENT_SYMBOL);
272             if (exponent < 0 && notation.exponentSignDisplay != SignDisplay.NEVER) {
273                 i += output.insert(i, symbols.getMinusSignString(), NumberFormat.Field.EXPONENT_SIGN);
274             } else if (exponent >= 0 && notation.exponentSignDisplay == SignDisplay.ALWAYS) {
275                 i += output.insert(i, symbols.getPlusSignString(), NumberFormat.Field.EXPONENT_SIGN);
276             }
277             // Append the exponent digits (using a simple inline algorithm)
278             int disp = Math.abs(exponent);
279             for (int j = 0; j < notation.minExponentDigits || disp > 0; j++, disp /= 10) {
280                 int d = disp % 10;
281                 String digitString = symbols.getDigitStringsLocal()[d];
282                 i += output.insert(i - j, digitString, NumberFormat.Field.EXPONENT);
283             }
284             return i - rightIndex;
285         }
286     }
287 
288     private static class ScientificModifier implements Modifier {
289         final int exponent;
290         final ScientificHandler handler;
291 
ScientificModifier(int exponent, ScientificHandler handler)292         ScientificModifier(int exponent, ScientificHandler handler) {
293             this.exponent = exponent;
294             this.handler = handler;
295         }
296 
297         @Override
apply(FormattedStringBuilder output, int leftIndex, int rightIndex)298         public int apply(FormattedStringBuilder output, int leftIndex, int rightIndex) {
299             return handler.doApply(exponent, output, rightIndex);
300         }
301 
302         @Override
getPrefixLength()303         public int getPrefixLength() {
304             // TODO: Localized exponent separator location.
305             return 0;
306         }
307 
308         @Override
getCodePointCount()309         public int getCodePointCount() {
310             // NOTE: This method is only called one place, NumberRangeFormatterImpl.
311             // The call site only cares about != 0 and != 1.
312             // Return a very large value so that if this method is used elsewhere, we should notice.
313             return 999;
314         }
315 
316         @Override
isStrong()317         public boolean isStrong() {
318             // Scientific is always strong
319             return true;
320         }
321 
322         @Override
containsField(Field field)323         public boolean containsField(Field field) {
324             // This method is not used for inner modifiers.
325             assert false;
326             return false;
327         }
328 
329         @Override
getParameters()330         public Parameters getParameters() {
331             return null;
332         }
333 
334         @Override
strictEquals(Modifier other)335         public boolean strictEquals(Modifier other) {
336             if (!(other instanceof ScientificModifier)) {
337                 return false;
338             }
339             ScientificModifier _other = (ScientificModifier) other;
340             // TODO: Check for locale symbols and settings as well? Could be less efficient.
341             return exponent == _other.exponent;
342         }
343     }
344 }
345