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