OLD | NEW |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 part of intl; | 5 part of intl; |
6 | 6 |
7 /** | 7 /// The function that we pass internally to NumberFormat to get |
8 * Provides the ability to format a number in a locale-specific way. The | 8 /// the appropriate pattern (e.g. currency) |
9 * format is specified as a pattern using a subset of the ICU formatting | 9 typedef String _PatternGetter(NumberSymbols symbols); |
10 * patterns. | 10 |
11 * | 11 /// Provides the ability to format a number in a locale-specific way. The |
12 * - `0` A single digit | 12 /// format is specified as a pattern using a subset of the ICU formatting |
13 * - `#` A single digit, omitted if the value is zero | 13 /// patterns. |
14 * - `.` Decimal separator | 14 /// |
15 * - `-` Minus sign | 15 /// - `0` A single digit |
16 * - `,` Grouping separator | 16 /// - `#` A single digit, omitted if the value is zero |
17 * - `E` Separates mantissa and expontent | 17 /// - `.` Decimal separator |
18 * - `+` - Before an exponent, indicates it should be prefixed with a plus sign. | 18 /// - `-` Minus sign |
19 * - `%` - In prefix or suffix, multiply by 100 and show as percentage | 19 /// - `,` Grouping separator |
20 * - `‰ (\u2030)` In prefix or suffix, multiply by 1000 and show as per mille | 20 /// - `E` Separates mantissa and expontent |
21 * - `¤ (\u00A4)` Currency sign, replaced by currency name | 21 /// - `+` - Before an exponent, to say it should be prefixed with a plus sign. |
22 * - `'` Used to quote special characters | 22 /// - `%` - In prefix or suffix, multiply by 100 and show as percentage |
23 * - `;` Used to separate the positive and negative patterns if both are present | 23 /// - `‰ (\u2030)` In prefix or suffix, multiply by 1000 and show as per mille |
24 * | 24 /// - `¤ (\u00A4)` Currency sign, replaced by currency name |
25 * For example, | 25 /// - `'` Used to quote special characters |
26 * var f = new NumberFormat("###.0#", "en_US"); | 26 /// - `;` Used to separate the positive and negative patterns (if both present) |
27 * print(f.format(12.345)); | 27 /// |
28 * ==> 12.34 | 28 /// For example, |
29 * If the locale is not specified, it will default to the current locale. If | 29 /// var f = new NumberFormat("###.0#", "en_US"); |
30 * the format is not specified it will print in a basic format with at least | 30 /// print(f.format(12.345)); |
31 * one integer digit and three fraction digits. | 31 /// ==> 12.34 |
32 * | 32 /// If the locale is not specified, it will default to the current locale. If |
33 * There are also standard patterns available via the special constructors. e.g. | 33 /// the format is not specified it will print in a basic format with at least |
34 * var percent = new NumberFormat.percentFormat("ar"); | 34 /// one integer digit and three fraction digits. |
35 * var eurosInUSFormat = new NumberFormat.currencyPattern("en_US", "€"); | 35 /// |
36 * There are four such constructors: decimalFormat, percentFormat, | 36 /// There are also standard patterns available via the special constructors. |
37 * scientificFormat and currencyFormat. However, at the moment, | 37 /// e.g. |
38 * scientificFormat prints only as equivalent to "#E0" and does not take | 38 /// var percent = new NumberFormat.percentFormat("ar"); |
39 * into account significant digits. The currencyFormat will default to the | 39 /// var eurosInUSFormat = new NumberFormat.currency(locale: "en_US", |
40 * three-letter name of the currency if no explicit name/symbol is provided. | 40 /// symbol: "€"); |
41 */ | 41 /// There are four such constructors: decimalFormat, percentFormat, |
| 42 /// scientificFormat and currencyFormat. However, at the moment, |
| 43 /// scientificFormat prints only as equivalent to "#E0" and does not take |
| 44 /// into account significant digits. The currencyFormat will default to the |
| 45 /// three-letter name of the currency if no explicit name/symbol is provided. |
42 class NumberFormat { | 46 class NumberFormat { |
43 /** Variables to determine how number printing behaves. */ | 47 /// Variables to determine how number printing behaves. |
44 // TODO(alanknight): If these remain as variables and are set based on the | 48 // TODO(alanknight): If these remain as variables and are set based on the |
45 // pattern, can we make them final? | 49 // pattern, can we make them final? |
46 String _negativePrefix = '-'; | 50 String _negativePrefix = '-'; |
47 String _positivePrefix = ''; | 51 String _positivePrefix = ''; |
48 String _negativeSuffix = ''; | 52 String _negativeSuffix = ''; |
49 String _positiveSuffix = ''; | 53 String _positiveSuffix = ''; |
50 /** | 54 |
51 * How many numbers in a group when using punctuation to group digits in | 55 /// How many numbers in a group when using punctuation to group digits in |
52 * large numbers. e.g. in en_US: "1,000,000" has a grouping size of 3 digits | 56 /// large numbers. e.g. in en_US: "1,000,000" has a grouping size of 3 digits |
53 * between commas. | 57 /// between commas. |
54 */ | |
55 int _groupingSize = 3; | 58 int _groupingSize = 3; |
56 /** | 59 |
57 * In some formats the last grouping size may be different than previous | 60 /// In some formats the last grouping size may be different than previous |
58 * ones, e.g. Hindi. | 61 /// ones, e.g. Hindi. |
59 */ | |
60 int _finalGroupingSize = 3; | 62 int _finalGroupingSize = 3; |
61 /** | 63 |
62 * Set to true if the format has explicitly set the grouping size. | 64 /// Set to true if the format has explicitly set the grouping size. |
63 */ | |
64 bool _groupingSizeSetExplicitly = false; | 65 bool _groupingSizeSetExplicitly = false; |
65 bool _decimalSeparatorAlwaysShown = false; | 66 bool _decimalSeparatorAlwaysShown = false; |
66 bool _useSignForPositiveExponent = false; | 67 bool _useSignForPositiveExponent = false; |
67 bool _useExponentialNotation = false; | 68 bool _useExponentialNotation = false; |
68 | 69 |
| 70 /// Explicitly store if we are a currency format, and so should use the |
| 71 /// appropriate number of decimal digits for a currency. |
| 72 // TODO(alanknight): Handle currency formats which are specified in a raw |
| 73 /// pattern, not using one of the currency constructors. |
| 74 bool _isForCurrency = false; |
| 75 |
69 int maximumIntegerDigits = 40; | 76 int maximumIntegerDigits = 40; |
70 int minimumIntegerDigits = 1; | 77 int minimumIntegerDigits = 1; |
71 int maximumFractionDigits = 3; | 78 int maximumFractionDigits = 3; |
72 int minimumFractionDigits = 0; | 79 int minimumFractionDigits = 0; |
73 int minimumExponentDigits = 0; | 80 int minimumExponentDigits = 0; |
74 | 81 int _significantDigits = 0; |
75 /** | 82 |
76 * For percent and permille, what are we multiplying by in order to | 83 /// How many significant digits should we print. |
77 * get the printed value, e.g. 100 for percent. | 84 /// |
78 */ | 85 /// Note that if significantDigitsInUse is the default false, this |
| 86 /// will be ignored. |
| 87 int get significantDigits => _significantDigits; |
| 88 set significantDigits(int x) { |
| 89 _significantDigits = x; |
| 90 significantDigitsInUse = true; |
| 91 } |
| 92 |
| 93 bool significantDigitsInUse = false; |
| 94 |
| 95 /// For percent and permille, what are we multiplying by in order to |
| 96 /// get the printed value, e.g. 100 for percent. |
79 int get _multiplier => _internalMultiplier; | 97 int get _multiplier => _internalMultiplier; |
80 set _multiplier(int x) { | 98 set _multiplier(int x) { |
81 _internalMultiplier = x; | 99 _internalMultiplier = x; |
82 _multiplierDigits = (log(_multiplier) / LN10).round(); | 100 _multiplierDigits = (log(_multiplier) / LN10).round(); |
83 } | 101 } |
| 102 |
84 int _internalMultiplier = 1; | 103 int _internalMultiplier = 1; |
85 | 104 |
86 /** How many digits are there in the [_multiplier]. */ | 105 /// How many digits are there in the [_multiplier]. |
87 int _multiplierDigits = 0; | 106 int _multiplierDigits = 0; |
88 | 107 |
89 /** | 108 /// Stores the pattern used to create this format. This isn't used, but |
90 * Stores the pattern used to create this format. This isn't used, but | 109 /// is helpful in debugging. |
91 * is helpful in debugging. | |
92 */ | |
93 String _pattern; | 110 String _pattern; |
94 | 111 |
95 /** The locale in which we print numbers. */ | 112 /// The locale in which we print numbers. |
96 final String _locale; | 113 final String _locale; |
97 | 114 |
98 /** Caches the symbols used for our locale. */ | 115 /// Caches the symbols used for our locale. |
99 NumberSymbols _symbols; | 116 NumberSymbols _symbols; |
100 | 117 |
101 /** The name (or symbol) of the currency to print. */ | 118 /// The name of the currency to print, in ISO 4217 form. |
102 String currencyName; | 119 String currencyName; |
103 | 120 |
104 /** | 121 /// The symbol to be used when formatting this as currency. |
105 * Transient internal state in which to build up the result of the format | 122 /// |
106 * operation. We can have this be just an instance variable because Dart is | 123 /// For example, "$", "US$", or "€". |
107 * single-threaded and unless we do an asynchronous operation in the process | 124 String _currencySymbol; |
108 * of formatting then there will only ever be one number being formatted | 125 |
109 * at a time. In languages with threads we'd need to pass this on the stack. | 126 /// The symbol to be used when formatting this as currency. |
110 */ | 127 /// |
| 128 /// For example, "$", "US$", or "€". |
| 129 String get currencySymbol => _currencySymbol ?? currencyName; |
| 130 |
| 131 /// The number of decimal places to use when formatting. |
| 132 /// |
| 133 /// If this is not explicitly specified in the constructor, then for |
| 134 /// currencies we use the default value for the currency if the name is given, |
| 135 /// otherwise we use the value from the pattern for the locale. |
| 136 /// |
| 137 /// So, for example, |
| 138 /// new NumberFormat.currency(name: 'USD', decimalDigits: 7) |
| 139 /// will format with 7 decimal digits, because that's what we asked for. But |
| 140 /// new NumberFormat.currency(locale: 'en_US', name: 'JPY') |
| 141 /// will format with zero, because that's the default for JPY, and the |
| 142 /// currency's default takes priority over the locale's default. |
| 143 /// new NumberFormat.currency(locale: 'en_US') |
| 144 /// will format with two, which is the default for that locale. |
| 145 /// |
| 146 int get decimalDigits => _decimalDigits; |
| 147 |
| 148 int _decimalDigits; |
| 149 |
| 150 /// For currencies, the default number of decimal places to use in |
| 151 /// formatting. Defaults to two for non-currencies or currencies where it's |
| 152 /// not specified. |
| 153 int get _defaultDecimalDigits => |
| 154 currencyFractionDigits[currencyName.toUpperCase()] ?? |
| 155 currencyFractionDigits['DEFAULT']; |
| 156 |
| 157 /// If we have a currencyName, use the decimal digits for that currency, |
| 158 /// unless we've explicitly specified some other number. |
| 159 bool get _overridesDecimalDigits => decimalDigits != null || _isForCurrency; |
| 160 |
| 161 /// Transient internal state in which to build up the result of the format |
| 162 /// operation. We can have this be just an instance variable because Dart is |
| 163 /// single-threaded and unless we do an asynchronous operation in the process |
| 164 /// of formatting then there will only ever be one number being formatted |
| 165 /// at a time. In languages with threads we'd need to pass this on the stack. |
111 final StringBuffer _buffer = new StringBuffer(); | 166 final StringBuffer _buffer = new StringBuffer(); |
112 | 167 |
113 /** | 168 /// Create a number format that prints using [newPattern] as it applies in |
114 * Create a number format that prints using [newPattern] as it applies in | 169 /// [locale]. |
115 * [locale]. | |
116 */ | |
117 factory NumberFormat([String newPattern, String locale]) => | 170 factory NumberFormat([String newPattern, String locale]) => |
118 new NumberFormat._forPattern(locale, (x) => newPattern); | 171 new NumberFormat._forPattern(locale, (x) => newPattern); |
119 | 172 |
120 /** Create a number format that prints as DECIMAL_PATTERN. */ | 173 /// Create a number format that prints as DECIMAL_PATTERN. |
121 NumberFormat.decimalPattern([String locale]) | 174 NumberFormat.decimalPattern([String locale]) |
122 : this._forPattern(locale, (x) => x.DECIMAL_PATTERN); | 175 : this._forPattern(locale, (x) => x.DECIMAL_PATTERN); |
123 | 176 |
124 /** Create a number format that prints as PERCENT_PATTERN. */ | 177 /// Create a number format that prints as PERCENT_PATTERN. |
125 NumberFormat.percentPattern([String locale]) | 178 NumberFormat.percentPattern([String locale]) |
126 : this._forPattern(locale, (x) => x.PERCENT_PATTERN); | 179 : this._forPattern(locale, (x) => x.PERCENT_PATTERN); |
127 | 180 |
128 /** Create a number format that prints as SCIENTIFIC_PATTERN. */ | 181 /// Create a number format that prints as SCIENTIFIC_PATTERN. |
129 NumberFormat.scientificPattern([String locale]) | 182 NumberFormat.scientificPattern([String locale]) |
130 : this._forPattern(locale, (x) => x.SCIENTIFIC_PATTERN); | 183 : this._forPattern(locale, (x) => x.SCIENTIFIC_PATTERN); |
131 | 184 |
132 /** | 185 /// A regular expression to validate currency names are exactly three |
133 * Create a number format that prints as CURRENCY_PATTERN. If provided, | 186 /// alphabetic characters. |
134 * use [nameOrSymbol] in place of the default currency name. e.g. | 187 static final _checkCurrencyName = new RegExp(r'^[a-zA-Z]{3}$'); |
135 * var eurosInCurrentLocale = new NumberFormat | 188 |
136 * .currencyPattern(Intl.defaultLocale, "€"); | 189 /// Create a number format that prints as CURRENCY_PATTERN. (Deprecated: |
137 */ | 190 /// prefer NumberFormat.currency) |
138 NumberFormat.currencyPattern([String locale, String nameOrSymbol]) | 191 /// |
139 : this._forPattern(locale, (x) => x.CURRENCY_PATTERN, nameOrSymbol); | 192 /// If provided, |
140 | 193 /// use [nameOrSymbol] in place of the default currency name. e.g. |
141 /** | 194 /// var eurosInCurrentLocale = new NumberFormat |
142 * Create a number format that prints in a pattern we get from | 195 /// .currencyPattern(Intl.defaultLocale, "€"); |
143 * the [getPattern] function using the locale [locale]. | 196 @Deprecated("Use NumberFormat.currency") |
144 */ | 197 factory NumberFormat.currencyPattern( |
145 NumberFormat._forPattern(String locale, Function getPattern, | 198 [String locale, String currencyNameOrSymbol]) { |
146 [this.currencyName]) | 199 // If it looks like an iso4217 name, pass as name, otherwise as symbol. |
147 : _locale = Intl.verifiedLocale(locale, localeExists) { | 200 if (currencyNameOrSymbol != null && |
| 201 _checkCurrencyName.hasMatch(currencyNameOrSymbol)) { |
| 202 return new NumberFormat.currency( |
| 203 locale: locale, name: currencyNameOrSymbol); |
| 204 } else { |
| 205 return new NumberFormat.currency( |
| 206 locale: locale, symbol: currencyNameOrSymbol); |
| 207 } |
| 208 } |
| 209 |
| 210 /// Create a [NumberFormat] that formats using the locale's CURRENCY_PATTERN. |
| 211 /// |
| 212 /// If [locale] is not specified, it will use the current default locale. |
| 213 /// |
| 214 /// If [name] is specified, the currency with that ISO 4217 name will be used. |
| 215 /// Otherwise we will use the default currency name for the current locale. If |
| 216 /// no [symbol] is specified, we will use the currency name in the formatted |
| 217 /// result. e.g. |
| 218 /// var f = new NumberFormat.currency(locale: 'en_US', name: 'EUR') |
| 219 /// will format currency like "EUR1.23". If we did not specify the name, it |
| 220 /// would format like "USD1.23". |
| 221 /// |
| 222 /// If [symbol] is used, then that symbol will be used in formatting instead |
| 223 /// of the name. e.g. |
| 224 /// var eurosInCurrentLocale = new NumberFormat.currency(symbol: "€"); |
| 225 /// will format like "€1.23". Otherwise it will use the currency name. |
| 226 /// If this is not explicitly specified in the constructor, then for |
| 227 /// currencies we use the default value for the currency if the name is given, |
| 228 /// otherwise we use the value from the pattern for the locale. |
| 229 /// |
| 230 /// If [decimalDigits] is specified, numbers will format with that many digits |
| 231 /// after the decimal place. If it's not, they will use the default for the |
| 232 /// currency in [name], and the default currency for [locale] if the currency |
| 233 /// name is not specified. e.g. |
| 234 /// new NumberFormat.currency(name: 'USD', decimalDigits: 7) |
| 235 /// will format with 7 decimal digits, because that's what we asked for. But |
| 236 /// new NumberFormat.currency(locale: 'en_US', name: 'JPY') |
| 237 /// will format with zero, because that's the default for JPY, and the |
| 238 /// currency's default takes priority over the locale's default. |
| 239 /// new NumberFormat.currency(locale: 'en_US') |
| 240 /// will format with two, which is the default for that locale. |
| 241 // TODO(alanknight): Should we allow decimalDigits on other numbers. |
| 242 NumberFormat.currency( |
| 243 {String locale, String name, String symbol, int decimalDigits}) |
| 244 : this._forPattern(locale, (x) => x.CURRENCY_PATTERN, |
| 245 name: name, |
| 246 currencySymbol: symbol, |
| 247 decimalDigits: decimalDigits, |
| 248 isForCurrency: true); |
| 249 |
| 250 /// Creates a [NumberFormat] for currencies, using the simple symbol for the |
| 251 /// currency if one is available (e.g. $, €), so it should only be used if the |
| 252 /// short currency symbol will be unambiguous. |
| 253 /// |
| 254 /// If [locale] is not specified, it will use the current default locale. |
| 255 /// |
| 256 /// If [name] is specified, the currency with that ISO 4217 name will be used. |
| 257 /// Otherwise we will use the default currency name for the current locale. We |
| 258 /// will assume that the symbol for this is well known in the locale and |
| 259 /// unambiguous. If you format CAD in an en_US locale using this format it |
| 260 /// will display as "$", which may be confusing to the user. |
| 261 /// |
| 262 /// If [decimalDigits] is specified, numbers will format with that many digits |
| 263 /// after the decimal place. If it's not, they will use the default for the |
| 264 /// currency in [name], and the default currency for [locale] if the currency |
| 265 /// name is not specified. e.g. |
| 266 /// new NumberFormat.simpleCurrency(name: 'USD', decimalDigits: 7) |
| 267 /// will format with 7 decimal digits, because that's what we asked for. But |
| 268 /// new NumberFormat.simpleCurrency(locale: 'en_US', name: 'JPY') |
| 269 /// will format with zero, because that's the default for JPY, and the |
| 270 /// currency's default takes priority over the locale's default. |
| 271 /// new NumberFormat.simpleCurrency(locale: 'en_US') |
| 272 /// will format with two, which is the default for that locale. |
| 273 factory NumberFormat.simpleCurrency( |
| 274 {String locale, String name, int decimalDigits}) { |
| 275 return new NumberFormat._forPattern(locale, (x) => x.CURRENCY_PATTERN, |
| 276 name: name, |
| 277 computeCurrencySymbol: (format) => |
| 278 _simpleCurrencySymbols[format.currencyName] ?? format.currencyName, |
| 279 decimalDigits: decimalDigits, |
| 280 isForCurrency: true); |
| 281 } |
| 282 |
| 283 /// Returns the simple currency symbol for given currency code, or |
| 284 /// [currencyCode] if no simple symbol is listed. |
| 285 /// |
| 286 /// The simple currency symbol is generally short, and the same or related to |
| 287 /// what is used in countries having the currency as an official symbol. It |
| 288 /// may be a symbol character, or may have letters, or both. It may be |
| 289 /// different according to the locale: for example, for an Arabic locale it |
| 290 /// may consist of Arabic letters, but for a French locale consist of Latin |
| 291 /// letters. It will not be unique: for example, "$" can appear for both USD |
| 292 /// and CAD. |
| 293 /// |
| 294 /// (The current implementation is the same for all locales, but this is |
| 295 /// temporary and callers shouldn't rely on it.) |
| 296 String simpleCurrencySymbol(String currencyCode) => |
| 297 _simpleCurrencySymbols[currencyCode] ?? currencyCode; |
| 298 |
| 299 /// A map from currency names to the simple name/symbol. |
| 300 /// |
| 301 /// The simple currency symbol is generally short, and the same or related to |
| 302 /// what is used in countries having the currency as an official symbol. It |
| 303 /// may be a symbol character, or may have letters, or both. It may be |
| 304 /// different according to the locale: for example, for an Arabic locale it |
| 305 /// may consist of Arabic letters, but for a French locale consist of Latin |
| 306 /// letters. It will not be unique: for example, "$" can appear for both USD |
| 307 /// and CAD. |
| 308 /// |
| 309 /// (The current implementation is the same for all locales, but this is |
| 310 /// temporary and callers shouldn't rely on it.) |
| 311 static Map<String, String> _simpleCurrencySymbols = { |
| 312 "AFN": "Af.", |
| 313 "TOP": r"T$", |
| 314 "MGA": "Ar", |
| 315 "THB": "\u0e3f", |
| 316 "PAB": "B/.", |
| 317 "ETB": "Birr", |
| 318 "VEF": "Bs", |
| 319 "BOB": "Bs", |
| 320 "GHS": "GHS", |
| 321 "CRC": "\u20a1", |
| 322 "NIO": r"C$", |
| 323 "GMD": "GMD", |
| 324 "MKD": "din", |
| 325 "BHD": "din", |
| 326 "DZD": "din", |
| 327 "IQD": "din", |
| 328 "JOD": "din", |
| 329 "KWD": "din", |
| 330 "LYD": "din", |
| 331 "RSD": "din", |
| 332 "TND": "din", |
| 333 "AED": "dh", |
| 334 "MAD": "dh", |
| 335 "STD": "Db", |
| 336 "BSD": r"$", |
| 337 "FJD": r"$", |
| 338 "GYD": r"$", |
| 339 "KYD": r"$", |
| 340 "LRD": r"$", |
| 341 "SBD": r"$", |
| 342 "SRD": r"$", |
| 343 "AUD": r"$", |
| 344 "BBD": r"$", |
| 345 "BMD": r"$", |
| 346 "BND": r"$", |
| 347 "BZD": r"$", |
| 348 "CAD": r"$", |
| 349 "HKD": r"$", |
| 350 "JMD": r"$", |
| 351 "NAD": r"$", |
| 352 "NZD": r"$", |
| 353 "SGD": r"$", |
| 354 "TTD": r"$", |
| 355 "TWD": r"NT$", |
| 356 "USD": r"$", |
| 357 "XCD": r"$", |
| 358 "VND": "\u20ab", |
| 359 "AMD": "Dram", |
| 360 "CVE": "CVE", |
| 361 "EUR": "\u20ac", |
| 362 "AWG": "Afl.", |
| 363 "HUF": "Ft", |
| 364 "BIF": "FBu", |
| 365 "CDF": "FrCD", |
| 366 "CHF": "CHF", |
| 367 "DJF": "Fdj", |
| 368 "GNF": "FG", |
| 369 "RWF": "RF", |
| 370 "XOF": "CFA", |
| 371 "XPF": "FCFP", |
| 372 "KMF": "CF", |
| 373 "XAF": "FCFA", |
| 374 "HTG": "HTG", |
| 375 "PYG": "Gs", |
| 376 "UAH": "\u20b4", |
| 377 "PGK": "PGK", |
| 378 "LAK": "\u20ad", |
| 379 "CZK": "K\u010d", |
| 380 "SEK": "kr", |
| 381 "ISK": "kr", |
| 382 "DKK": "kr", |
| 383 "NOK": "kr", |
| 384 "HRK": "kn", |
| 385 "MWK": "MWK", |
| 386 "ZMK": "ZWK", |
| 387 "AOA": "Kz", |
| 388 "MMK": "K", |
| 389 "GEL": "GEL", |
| 390 "LVL": "Ls", |
| 391 "ALL": "Lek", |
| 392 "HNL": "L", |
| 393 "SLL": "SLL", |
| 394 "MDL": "MDL", |
| 395 "RON": "RON", |
| 396 "BGN": "lev", |
| 397 "SZL": "SZL", |
| 398 "TRY": "TL", |
| 399 "LTL": "Lt", |
| 400 "LSL": "LSL", |
| 401 "AZN": "man.", |
| 402 "BAM": "KM", |
| 403 "MZN": "MTn", |
| 404 "NGN": "\u20a6", |
| 405 "ERN": "Nfk", |
| 406 "BTN": "Nu.", |
| 407 "MRO": "MRO", |
| 408 "MOP": "MOP", |
| 409 "CUP": r"$", |
| 410 "CUC": r"$", |
| 411 "ARS": r"$", |
| 412 "CLF": "UF", |
| 413 "CLP": r"$", |
| 414 "COP": r"$", |
| 415 "DOP": r"$", |
| 416 "MXN": r"$", |
| 417 "PHP": "\u20b1", |
| 418 "UYU": r"$", |
| 419 "FKP": "£", |
| 420 "GIP": "£", |
| 421 "SHP": "£", |
| 422 "EGP": "E£", |
| 423 "LBP": "L£", |
| 424 "SDG": "SDG", |
| 425 "SSP": "SSP", |
| 426 "GBP": "£", |
| 427 "SYP": "£", |
| 428 "BWP": "P", |
| 429 "GTQ": "Q", |
| 430 "ZAR": "R", |
| 431 "BRL": r"R$", |
| 432 "OMR": "Rial", |
| 433 "QAR": "Rial", |
| 434 "YER": "Rial", |
| 435 "IRR": "Rial", |
| 436 "KHR": "Riel", |
| 437 "MYR": "RM", |
| 438 "SAR": "Rial", |
| 439 "BYR": "BYR", |
| 440 "RUB": "руб.", |
| 441 "MUR": "Rs", |
| 442 "SCR": "SCR", |
| 443 "LKR": "Rs", |
| 444 "NPR": "Rs", |
| 445 "INR": "\u20b9", |
| 446 "PKR": "Rs", |
| 447 "IDR": "Rp", |
| 448 "ILS": "\u20aa", |
| 449 "KES": "Ksh", |
| 450 "SOS": "SOS", |
| 451 "TZS": "TSh", |
| 452 "UGX": "UGX", |
| 453 "PEN": "S/.", |
| 454 "KGS": "KGS", |
| 455 "UZS": "so\u02bcm", |
| 456 "TJS": "Som", |
| 457 "BDT": "\u09f3", |
| 458 "WST": "WST", |
| 459 "KZT": "\u20b8", |
| 460 "MNT": "\u20ae", |
| 461 "VUV": "VUV", |
| 462 "KPW": "\u20a9", |
| 463 "KRW": "\u20a9", |
| 464 "JPY": "Â¥", |
| 465 "CNY": "Â¥", |
| 466 "PLN": "z\u0142", |
| 467 "MVR": "Rf", |
| 468 "NLG": "NAf", |
| 469 "ZMW": "ZK", |
| 470 "ANG": "Æ’", |
| 471 "TMT": "TMT", |
| 472 }; |
| 473 |
| 474 /// Create a number format that prints in a pattern we get from |
| 475 /// the [getPattern] function using the locale [locale]. |
| 476 /// |
| 477 /// The [currencySymbol] can either be specified directly, or we can pass a |
| 478 /// function [computeCurrencySymbol] that will compute it later, given other |
| 479 /// information, typically the verified locale. |
| 480 NumberFormat._forPattern(String locale, _PatternGetter getPattern, |
| 481 {String name, |
| 482 String currencySymbol, |
| 483 String computeCurrencySymbol(NumberFormat), |
| 484 int decimalDigits, |
| 485 bool isForCurrency: false}) |
| 486 : _locale = Intl.verifiedLocale(locale, localeExists), |
| 487 _isForCurrency = isForCurrency { |
| 488 this._currencySymbol = currencySymbol; |
| 489 this._decimalDigits = decimalDigits; |
148 _symbols = numberFormatSymbols[_locale]; | 490 _symbols = numberFormatSymbols[_locale]; |
149 if (currencyName == null) { | 491 _localeZero = _symbols.ZERO_DIGIT.codeUnitAt(0); |
150 currencyName = _symbols.DEF_CURRENCY_CODE; | 492 _zeroOffset = _localeZero - _zero; |
| 493 _negativePrefix = _symbols.MINUS_SIGN; |
| 494 currencyName = name ?? _symbols.DEF_CURRENCY_CODE; |
| 495 if (this._currencySymbol == null && computeCurrencySymbol != null) { |
| 496 this._currencySymbol = computeCurrencySymbol(this); |
151 } | 497 } |
152 _setPattern(getPattern(_symbols)); | 498 _setPattern(getPattern(_symbols)); |
153 } | 499 } |
154 | 500 |
155 /** | 501 /// A number format for compact representations, e.g. "1.2M" instead |
156 * Return the locale code in which we operate, e.g. 'en_US' or 'pt'. | 502 /// of "1,200,000". |
157 */ | 503 factory NumberFormat.compact({String locale}) { |
| 504 return new _CompactNumberFormat( |
| 505 locale: locale, |
| 506 formatType: _CompactFormatType.COMPACT_DECIMAL_SHORT_PATTERN); |
| 507 } |
| 508 |
| 509 /// A number format for "long" compact representations, e.g. "1.2 million" |
| 510 /// instead of of "1,200,000". |
| 511 factory NumberFormat.compactLong({String locale}) { |
| 512 return new _CompactNumberFormat( |
| 513 locale: locale, |
| 514 formatType: _CompactFormatType.COMPACT_DECIMAL_LONG_PATTERN); |
| 515 } |
| 516 |
| 517 /// A number format for compact currency representations, e.g. "$1.2M" instead |
| 518 /// of "$1,200,000", and which will automatically determine a currency symbol |
| 519 /// based on the currency name or the locale. See |
| 520 /// [NumberFormat.simpleCurrency]. |
| 521 factory NumberFormat.compactSimpleCurrency({String locale, String name}) { |
| 522 return new _CompactNumberFormat( |
| 523 locale: locale, |
| 524 formatType: _CompactFormatType.COMPACT_DECIMAL_SHORT_CURRENCY_PATTERN, |
| 525 name: name, |
| 526 getPattern: (symbols) => symbols.CURRENCY_PATTERN, |
| 527 computeCurrencySymbol: (format) => |
| 528 _simpleCurrencySymbols[format.currencyName] ?? format.currencyName, |
| 529 isForCurrency: true); |
| 530 } |
| 531 |
| 532 /// A number format for compact currency representations, e.g. "$1.2M" instead |
| 533 /// of "$1,200,000". |
| 534 factory NumberFormat.compactCurrency( |
| 535 {String locale, String name, String symbol, int decimalDigits}) { |
| 536 return new _CompactNumberFormat( |
| 537 locale: locale, |
| 538 formatType: _CompactFormatType.COMPACT_DECIMAL_SHORT_CURRENCY_PATTERN, |
| 539 name: name, |
| 540 getPattern: (symbols) => symbols.CURRENCY_PATTERN, |
| 541 currencySymbol: symbol, |
| 542 decimalDigits: decimalDigits, |
| 543 isForCurrency: true); |
| 544 } |
| 545 |
| 546 /// Return the locale code in which we operate, e.g. 'en_US' or 'pt'. |
158 String get locale => _locale; | 547 String get locale => _locale; |
159 | 548 |
160 /** | 549 /// Return true if the locale exists, or if it is null. The null case |
161 * Return true if the locale exists, or if it is null. The null case | 550 /// is interpreted to mean that we use the default locale. |
162 * is interpreted to mean that we use the default locale. | |
163 */ | |
164 static bool localeExists(localeName) { | 551 static bool localeExists(localeName) { |
165 if (localeName == null) return false; | 552 if (localeName == null) return false; |
166 return numberFormatSymbols.containsKey(localeName); | 553 return numberFormatSymbols.containsKey(localeName); |
167 } | 554 } |
168 | 555 |
169 /** | 556 /// Return the symbols which are used in our locale. Cache them to avoid |
170 * Return the symbols which are used in our locale. Cache them to avoid | 557 /// repeated lookup. |
171 * repeated lookup. | |
172 */ | |
173 NumberSymbols get symbols => _symbols; | 558 NumberSymbols get symbols => _symbols; |
174 | 559 |
175 /** | 560 /// Format [number] according to our pattern and return the formatted string. |
176 * Format [number] according to our pattern and return the formatted string. | |
177 */ | |
178 String format(number) { | 561 String format(number) { |
179 if (_isNaN(number)) return symbols.NAN; | 562 if (_isNaN(number)) return symbols.NAN; |
180 if (_isInfinite(number)) return "${_signPrefix(number)}${symbols.INFINITY}"; | 563 if (_isInfinite(number)) return "${_signPrefix(number)}${symbols.INFINITY}"; |
181 | 564 |
182 _add(_signPrefix(number)); | 565 _add(_signPrefix(number)); |
183 _formatNumber(number.abs()); | 566 _formatNumber(number.abs()); |
184 _add(_signSuffix(number)); | 567 _add(_signSuffix(number)); |
185 | 568 |
186 var result = _buffer.toString(); | 569 var result = _buffer.toString(); |
187 _buffer.clear(); | 570 _buffer.clear(); |
188 return result; | 571 return result; |
189 } | 572 } |
190 | 573 |
191 /** | 574 /// Parse the number represented by the string. If it's not |
192 * Parse the number represented by the string. If it's not | 575 /// parseable, throws a [FormatException]. |
193 * parseable, throws a [FormatException]. | |
194 */ | |
195 num parse(String text) => new _NumberParser(this, text).value; | 576 num parse(String text) => new _NumberParser(this, text).value; |
196 | 577 |
197 /** | 578 /// Format the main part of the number in the form dictated by the pattern. |
198 * Format the main part of the number in the form dictated by the pattern. | |
199 */ | |
200 void _formatNumber(number) { | 579 void _formatNumber(number) { |
201 if (_useExponentialNotation) { | 580 if (_useExponentialNotation) { |
202 _formatExponential(number); | 581 _formatExponential(number); |
203 } else { | 582 } else { |
204 _formatFixed(number); | 583 _formatFixed(number); |
205 } | 584 } |
206 } | 585 } |
207 | 586 |
208 /** Format the number in exponential notation. */ | 587 /// Format the number in exponential notation. |
209 void _formatExponential(num number) { | 588 void _formatExponential(num number) { |
210 if (number == 0.0) { | 589 if (number == 0.0) { |
211 _formatFixed(number); | 590 _formatFixed(number); |
212 _formatExponent(0); | 591 _formatExponent(0); |
213 return; | 592 return; |
214 } | 593 } |
215 | 594 |
216 var exponent = (log(number) / log(10)).floor(); | 595 var exponent = (log(number) / LN10).floor(); |
217 var mantissa = number / pow(10.0, exponent); | 596 var mantissa = number / pow(10.0, exponent); |
218 | 597 |
219 var minIntDigits = minimumIntegerDigits; | |
220 if (maximumIntegerDigits > 1 && | 598 if (maximumIntegerDigits > 1 && |
221 maximumIntegerDigits > minimumIntegerDigits) { | 599 maximumIntegerDigits > minimumIntegerDigits) { |
222 // A repeating range is defined; adjust to it as follows. | 600 // A repeating range is defined; adjust to it as follows. |
223 // If repeat == 3, we have 6,5,4=>3; 3,2,1=>0; 0,-1,-2=>-3; | 601 // If repeat == 3, we have 6,5,4=>3; 3,2,1=>0; 0,-1,-2=>-3; |
224 // -3,-4,-5=>-6, etc. This takes into account that the | 602 // -3,-4,-5=>-6, etc. This takes into account that the |
225 // exponent we have here is off by one from what we expect; | 603 // exponent we have here is off by one from what we expect; |
226 // it is for the format 0.MMMMMx10^n. | 604 // it is for the format 0.MMMMMx10^n. |
227 while ((exponent % maximumIntegerDigits) != 0) { | 605 while ((exponent % maximumIntegerDigits) != 0) { |
228 mantissa *= 10; | 606 mantissa *= 10; |
229 exponent--; | 607 exponent--; |
230 } | 608 } |
231 minIntDigits = 1; | |
232 } else { | 609 } else { |
233 // No repeating range is defined, use minimum integer digits. | 610 // No repeating range is defined, use minimum integer digits. |
234 if (minimumIntegerDigits < 1) { | 611 if (minimumIntegerDigits < 1) { |
235 exponent++; | 612 exponent++; |
236 mantissa /= 10; | 613 mantissa /= 10; |
237 } else { | 614 } else { |
238 exponent -= minimumIntegerDigits - 1; | 615 exponent -= minimumIntegerDigits - 1; |
239 mantissa *= pow(10, minimumIntegerDigits - 1); | 616 mantissa *= pow(10, minimumIntegerDigits - 1); |
240 } | 617 } |
241 } | 618 } |
242 _formatFixed(mantissa); | 619 _formatFixed(mantissa); |
243 _formatExponent(exponent); | 620 _formatExponent(exponent); |
244 } | 621 } |
245 | 622 |
246 /** | 623 /// Format the exponent portion, e.g. in "1.3e-5" the "e-5". |
247 * Format the exponent portion, e.g. in "1.3e-5" the "e-5". | |
248 */ | |
249 void _formatExponent(num exponent) { | 624 void _formatExponent(num exponent) { |
250 _add(symbols.EXP_SYMBOL); | 625 _add(symbols.EXP_SYMBOL); |
251 if (exponent < 0) { | 626 if (exponent < 0) { |
252 exponent = -exponent; | 627 exponent = -exponent; |
253 _add(symbols.MINUS_SIGN); | 628 _add(symbols.MINUS_SIGN); |
254 } else if (_useSignForPositiveExponent) { | 629 } else if (_useSignForPositiveExponent) { |
255 _add(symbols.PLUS_SIGN); | 630 _add(symbols.PLUS_SIGN); |
256 } | 631 } |
257 _pad(minimumExponentDigits, exponent.toString()); | 632 _pad(minimumExponentDigits, exponent.toString()); |
258 } | 633 } |
259 | 634 |
260 /** Used to test if we have exceeded Javascript integer limits. */ | 635 /// Used to test if we have exceeded Javascript integer limits. |
261 final _maxInt = pow(2, 52); | 636 final _maxInt = pow(2, 52); |
262 | 637 |
263 /** | 638 /// Helpers to check numbers that don't conform to the [num] interface, |
264 * Helpers to check numbers that don't conform to the [num] interface, | 639 /// e.g. Int64 |
265 * e.g. Int64 | |
266 */ | |
267 _isInfinite(number) => number is num ? number.isInfinite : false; | 640 _isInfinite(number) => number is num ? number.isInfinite : false; |
268 _isNaN(number) => number is num ? number.isNaN : false; | 641 _isNaN(number) => number is num ? number.isNaN : false; |
269 _round(number) => number is num ? number.round() : number; | |
270 _floor(number) => number is num ? number.floor() : number; | |
271 | 642 |
272 /** | 643 /// Helper to get the floor of a number which might not be num. This should |
273 * Format the basic number portion, inluding the fractional digits. | 644 /// only ever be called with an argument which is positive, or whose abs() |
274 */ | 645 /// is negative. The second case is the maximum negative value on a |
| 646 /// fixed-length integer. Since they are integers, they are also their own |
| 647 /// floor. |
| 648 _floor(number) { |
| 649 if (number.isNegative && !(number.abs().isNegative)) { |
| 650 throw new ArgumentError( |
| 651 "Internal error: expected positive number, got $number"); |
| 652 } |
| 653 return (number is num) ? number.floor() : number ~/ 1; |
| 654 } |
| 655 |
| 656 /// Helper to round a number which might not be num. |
| 657 _round(number) { |
| 658 if (number is num) { |
| 659 if (number.isInfinite) { |
| 660 return _maxInt; |
| 661 } else { |
| 662 return number.round(); |
| 663 } |
| 664 } else if (number.remainder(1) == 0) { |
| 665 // Not a normal number, but int-like, e.g. Int64 |
| 666 return number; |
| 667 } else { |
| 668 // TODO(alanknight): Do this more efficiently. If IntX had floor and roun
d we could avoid this. |
| 669 var basic = _floor(number); |
| 670 var fraction = (number - basic).toDouble().round(); |
| 671 return fraction == 0 ? number : number + fraction; |
| 672 } |
| 673 } |
| 674 |
| 675 // Return the number of digits left of the decimal place in [number]. |
| 676 static int numberOfIntegerDigits(number) { |
| 677 var simpleNumber = number.toDouble().abs(); |
| 678 // It's unfortunate that we have to do this, but we get precision errors |
| 679 // that affect the result if we use logs, e.g. 1000000 |
| 680 if (simpleNumber < 10) return 1; |
| 681 if (simpleNumber < 100) return 2; |
| 682 if (simpleNumber < 1000) return 3; |
| 683 if (simpleNumber < 10000) return 4; |
| 684 if (simpleNumber < 100000) return 5; |
| 685 if (simpleNumber < 1000000) return 6; |
| 686 if (simpleNumber < 10000000) return 7; |
| 687 if (simpleNumber < 100000000) return 8; |
| 688 if (simpleNumber < 1000000000) return 9; |
| 689 if (simpleNumber < 10000000000) return 10; |
| 690 if (simpleNumber < 100000000000) return 11; |
| 691 if (simpleNumber < 1000000000000) return 12; |
| 692 if (simpleNumber < 10000000000000) return 13; |
| 693 if (simpleNumber < 100000000000000) return 14; |
| 694 if (simpleNumber < 1000000000000000) return 15; |
| 695 if (simpleNumber < 10000000000000000) return 16; |
| 696 // We're past the point where being off by one on the number of digits |
| 697 // will affect the pattern, so now we can use logs. |
| 698 return max(1, (log(simpleNumber) / LN10).ceil()); |
| 699 } |
| 700 |
| 701 int _fractionDigitsAfter(int remainingSignificantDigits) => |
| 702 max(0, remainingSignificantDigits); |
| 703 |
| 704 /// Format the basic number portion, including the fractional digits. |
275 void _formatFixed(number) { | 705 void _formatFixed(number) { |
276 var integerPart; | 706 var integerPart; |
277 int fractionPart; | 707 int fractionPart; |
278 int extraIntegerDigits; | 708 int extraIntegerDigits; |
| 709 var fractionDigits = maximumFractionDigits; |
279 | 710 |
280 final power = pow(10, maximumFractionDigits); | 711 var power = 0; |
281 final digitMultiplier = power * _multiplier; | 712 var digitMultiplier; |
282 | 713 |
283 if (_isInfinite(number)) { | 714 if (_isInfinite(number)) { |
284 integerPart = number.toInt(); | 715 integerPart = number.toInt(); |
285 extraIntegerDigits = 0; | 716 extraIntegerDigits = 0; |
286 fractionPart = 0; | 717 fractionPart = 0; |
287 } else { | 718 } else { |
288 // We have three possible pieces. First, the basic integer part. If this | 719 // We have three possible pieces. First, the basic integer part. If this |
289 // is a percent or permille, the additional 2 or 3 digits. Finally the | 720 // is a percent or permille, the additional 2 or 3 digits. Finally the |
290 // fractional part. | 721 // fractional part. |
291 // We avoid multiplying the number because it might overflow if we have | 722 // We avoid multiplying the number because it might overflow if we have |
292 // a fixed-size integer type, so we extract each of the three as an | 723 // a fixed-size integer type, so we extract each of the three as an |
293 // integer pieces. | 724 // integer pieces. |
294 integerPart = _floor(number); | 725 integerPart = _floor(number); |
295 var fraction = number - integerPart; | 726 var fraction = number - integerPart; |
| 727 |
| 728 /// If we have significant digits, recalculate the number of fraction |
| 729 /// digits based on that. |
| 730 if (significantDigitsInUse) { |
| 731 var integerLength = numberOfIntegerDigits(integerPart); |
| 732 var remainingSignificantDigits = |
| 733 significantDigits - _multiplierDigits - integerLength; |
| 734 fractionDigits = _fractionDigitsAfter(remainingSignificantDigits); |
| 735 if (remainingSignificantDigits < 0) { |
| 736 // We may have to round. |
| 737 var divideBy = pow(10, integerLength - significantDigits); |
| 738 integerPart = (integerPart / divideBy).round() * divideBy; |
| 739 } |
| 740 } |
| 741 power = pow(10, fractionDigits); |
| 742 digitMultiplier = power * _multiplier; |
| 743 |
296 // Multiply out to the number of decimal places and the percent, then | 744 // Multiply out to the number of decimal places and the percent, then |
297 // round. For fixed-size integer types this should always be zero, so | 745 // round. For fixed-size integer types this should always be zero, so |
298 // multiplying is OK. | 746 // multiplying is OK. |
299 var remainingDigits = _round(fraction * digitMultiplier).toInt(); | 747 var remainingDigits = _round(fraction * digitMultiplier).toInt(); |
300 // However, in rounding we may overflow into the main digits. | 748 // However, in rounding we may overflow into the main digits. |
301 if (remainingDigits >= digitMultiplier) { | 749 if (remainingDigits >= digitMultiplier) { |
302 integerPart++; | 750 integerPart++; |
303 remainingDigits -= digitMultiplier; | 751 remainingDigits -= digitMultiplier; |
304 } | 752 } |
305 // Separate out the extra integer parts from the fraction part. | 753 // Separate out the extra integer parts from the fraction part. |
306 extraIntegerDigits = remainingDigits ~/ power; | 754 extraIntegerDigits = remainingDigits ~/ power; |
307 fractionPart = remainingDigits % power; | 755 fractionPart = remainingDigits % power; |
308 } | 756 } |
309 var fractionPresent = minimumFractionDigits > 0 || fractionPart > 0; | |
310 | 757 |
311 var integerDigits = _integerDigits(integerPart, extraIntegerDigits); | 758 var integerDigits = _integerDigits(integerPart, extraIntegerDigits); |
312 var digitLength = integerDigits.length; | 759 var digitLength = integerDigits.length; |
| 760 var fractionPresent = |
| 761 fractionDigits > 0 && (minimumFractionDigits > 0 || fractionPart > 0); |
313 | 762 |
314 if (_hasIntegerDigits(integerDigits)) { | 763 if (_hasIntegerDigits(integerDigits)) { |
315 _pad(minimumIntegerDigits - digitLength); | 764 _padEmpty(minimumIntegerDigits - digitLength); |
316 for (var i = 0; i < digitLength; i++) { | 765 for (var i = 0; i < digitLength; i++) { |
317 _addDigit(integerDigits.codeUnitAt(i)); | 766 _addDigit(integerDigits.codeUnitAt(i)); |
318 _group(digitLength, i); | 767 _group(digitLength, i); |
319 } | 768 } |
320 } else if (!fractionPresent) { | 769 } else if (!fractionPresent) { |
321 // If neither fraction nor integer part exists, just print zero. | 770 // If neither fraction nor integer part exists, just print zero. |
322 _addZero(); | 771 _addZero(); |
323 } | 772 } |
324 | 773 |
325 _decimalSeparator(fractionPresent); | 774 _decimalSeparator(fractionPresent); |
326 _formatFractionPart((fractionPart + power).toString()); | 775 _formatFractionPart((fractionPart + power).toString()); |
327 } | 776 } |
328 | 777 |
329 /** | 778 /// Compute the raw integer digits which will then be printed with |
330 * Compute the raw integer digits which will then be printed with | 779 /// grouping and translated to localized digits. |
331 * grouping and translated to localized digits. | |
332 */ | |
333 String _integerDigits(integerPart, extraIntegerDigits) { | 780 String _integerDigits(integerPart, extraIntegerDigits) { |
334 // If the int part is larger than 2^52 and we're on Javascript (so it's | 781 // If the int part is larger than 2^52 and we're on Javascript (so it's |
335 // really a float) it will lose precision, so pad out the rest of it | 782 // really a float) it will lose precision, so pad out the rest of it |
336 // with zeros. Check for Javascript by seeing if an integer is double. | 783 // with zeros. Check for Javascript by seeing if an integer is double. |
337 var paddingDigits = ''; | 784 var paddingDigits = ''; |
338 if (1 is double && integerPart is num && integerPart > _maxInt) { | 785 if (1 is double && integerPart is num && integerPart > _maxInt) { |
339 var howManyDigitsTooBig = (log(integerPart) / LN10).ceil() - 16; | 786 var howManyDigitsTooBig = (log(integerPart) / LN10).ceil() - 16; |
340 var divisor = pow(10, howManyDigitsTooBig).round(); | 787 var divisor = pow(10, howManyDigitsTooBig).round(); |
341 paddingDigits = symbols.ZERO_DIGIT * howManyDigitsTooBig.toInt(); | 788 paddingDigits = '0' * howManyDigitsTooBig.toInt(); |
342 integerPart = (integerPart / divisor).truncate(); | 789 integerPart = (integerPart / divisor).truncate(); |
343 } | 790 } |
344 | 791 |
345 var extra = extraIntegerDigits == 0 ? '' : extraIntegerDigits.toString(); | 792 var extra = extraIntegerDigits == 0 ? '' : extraIntegerDigits.toString(); |
346 var intDigits = _mainIntegerDigits(integerPart); | 793 var intDigits = _mainIntegerDigits(integerPart); |
347 var paddedExtra = | 794 var paddedExtra = |
348 intDigits.isEmpty ? extra : extra.padLeft(_multiplierDigits, '0'); | 795 intDigits.isEmpty ? extra : extra.padLeft(_multiplierDigits, '0'); |
349 return "${intDigits}${paddedExtra}${paddingDigits}"; | 796 return "${intDigits}${paddedExtra}${paddingDigits}"; |
350 } | 797 } |
351 | 798 |
352 /** | 799 /// The digit string of the integer part. This is the empty string if the |
353 * The digit string of the integer part. This is the empty string if the | 800 /// integer part is zero and otherwise is the toString() of the integer |
354 * integer part is zero and otherwise is the toString() of the integer | 801 /// part, stripping off any minus sign. |
355 * part, stripping off any minus sign. | |
356 */ | |
357 String _mainIntegerDigits(integer) { | 802 String _mainIntegerDigits(integer) { |
358 if (integer == 0) return ''; | 803 if (integer == 0) return ''; |
359 var digits = integer.toString(); | 804 var digits = integer.toString(); |
| 805 if (significantDigitsInUse && digits.length > significantDigits) { |
| 806 digits = digits.substring(0, significantDigits) + |
| 807 ''.padLeft(digits.length - significantDigits, '0'); |
| 808 } |
360 // If we have a fixed-length int representation, it can have a negative | 809 // If we have a fixed-length int representation, it can have a negative |
361 // number whose negation is also negative, e.g. 2^-63 in 64-bit. | 810 // number whose negation is also negative, e.g. 2^-63 in 64-bit. |
362 // Remove the minus sign. | 811 // Remove the minus sign. |
363 return digits.startsWith('-') ? digits.substring(1) : digits; | 812 return digits.startsWith('-') ? digits.substring(1) : digits; |
364 } | 813 } |
365 | 814 |
366 /** | 815 /// Format the part after the decimal place in a fixed point number. |
367 * Format the part after the decimal place in a fixed point number. | |
368 */ | |
369 void _formatFractionPart(String fractionPart) { | 816 void _formatFractionPart(String fractionPart) { |
370 var fractionCodes = fractionPart.codeUnits; | |
371 var fractionLength = fractionPart.length; | 817 var fractionLength = fractionPart.length; |
372 while (fractionCodes[fractionLength - 1] == _zero && | 818 while (fractionPart.codeUnitAt(fractionLength - 1) == _zero && |
373 fractionLength > minimumFractionDigits + 1) { | 819 fractionLength > minimumFractionDigits + 1) { |
374 fractionLength--; | 820 fractionLength--; |
375 } | 821 } |
376 for (var i = 1; i < fractionLength; i++) { | 822 for (var i = 1; i < fractionLength; i++) { |
377 _addDigit(fractionCodes[i]); | 823 _addDigit(fractionPart.codeUnitAt(i)); |
378 } | 824 } |
379 } | 825 } |
380 | 826 |
381 /** Print the decimal separator if appropriate. */ | 827 /// Print the decimal separator if appropriate. |
382 void _decimalSeparator(bool fractionPresent) { | 828 void _decimalSeparator(bool fractionPresent) { |
383 if (_decimalSeparatorAlwaysShown || fractionPresent) { | 829 if (_decimalSeparatorAlwaysShown || fractionPresent) { |
384 _add(symbols.DECIMAL_SEP); | 830 _add(symbols.DECIMAL_SEP); |
385 } | 831 } |
386 } | 832 } |
387 | 833 |
388 /** | 834 /// Return true if we have a main integer part which is printable, either |
389 * Return true if we have a main integer part which is printable, either | 835 /// because we have digits left of the decimal point (this may include digits |
390 * because we have digits left of the decimal point (this may include digits | 836 /// which have been moved left because of percent or permille formatting), |
391 * which have been moved left because of percent or permille formatting), | 837 /// or because the minimum number of printable digits is greater than 1. |
392 * or because the minimum number of printable digits is greater than 1. | |
393 */ | |
394 bool _hasIntegerDigits(String digits) => | 838 bool _hasIntegerDigits(String digits) => |
395 digits.isNotEmpty || minimumIntegerDigits > 0; | 839 digits.isNotEmpty || minimumIntegerDigits > 0; |
396 | 840 |
397 /** A group of methods that provide support for writing digits and other | 841 /// A group of methods that provide support for writing digits and other |
398 * required characters into [_buffer] easily. | 842 /// required characters into [_buffer] easily. |
399 */ | |
400 void _add(String x) { | 843 void _add(String x) { |
401 _buffer.write(x); | 844 _buffer.write(x); |
402 } | 845 } |
403 void _addCharCode(int x) { | 846 |
404 _buffer.writeCharCode(x); | |
405 } | |
406 void _addZero() { | 847 void _addZero() { |
407 _buffer.write(symbols.ZERO_DIGIT); | 848 _buffer.write(symbols.ZERO_DIGIT); |
408 } | 849 } |
| 850 |
409 void _addDigit(int x) { | 851 void _addDigit(int x) { |
410 _buffer.writeCharCode(_localeZero + x - _zero); | 852 _buffer.writeCharCode(x + _zeroOffset); |
411 } | 853 } |
412 | 854 |
413 /** Print padding up to [numberOfDigits] above what's included in [basic]. */ | 855 void _padEmpty(int howMany) { |
414 void _pad(int numberOfDigits, [String basic = '']) { | 856 _buffer.write(symbols.ZERO_DIGIT * howMany); |
| 857 } |
| 858 |
| 859 void _pad(int numberOfDigits, String basic) { |
| 860 if (_zeroOffset == 0) { |
| 861 _buffer.write(basic.padLeft(numberOfDigits, '0')); |
| 862 } else { |
| 863 _slowPad(numberOfDigits, basic); |
| 864 } |
| 865 } |
| 866 |
| 867 /// Print padding up to [numberOfDigits] above what's included in [basic]. |
| 868 void _slowPad(int numberOfDigits, String basic) { |
415 for (var i = 0; i < numberOfDigits - basic.length; i++) { | 869 for (var i = 0; i < numberOfDigits - basic.length; i++) { |
416 _add(symbols.ZERO_DIGIT); | 870 _add(symbols.ZERO_DIGIT); |
417 } | 871 } |
418 for (var x in basic.codeUnits) { | 872 for (int i = 0; i < basic.length; i++) { |
419 _addDigit(x); | 873 _addDigit(basic.codeUnitAt(i)); |
420 } | 874 } |
421 } | 875 } |
422 | 876 |
423 /** | 877 /// We are printing the digits of the number from left to right. We may need |
424 * We are printing the digits of the number from left to right. We may need | 878 /// to print a thousands separator or other grouping character as appropriate |
425 * to print a thousands separator or other grouping character as appropriate | 879 /// to the locale. So we find how many places we are from the end of the numbe
r |
426 * to the locale. So we find how many places we are from the end of the number | 880 /// by subtracting our current [position] from the [totalLength] and printing |
427 * by subtracting our current [position] from the [totalLength] and printing | 881 /// the separator character every [_groupingSize] digits, with the final |
428 * the separator character every [_groupingSize] digits, with the final | 882 /// grouping possibly being of a different size, [_finalGroupingSize]. |
429 * grouping possibly being of a different size, [_finalGroupingSize]. | |
430 */ | |
431 void _group(int totalLength, int position) { | 883 void _group(int totalLength, int position) { |
432 var distanceFromEnd = totalLength - position; | 884 var distanceFromEnd = totalLength - position; |
433 if (distanceFromEnd <= 1 || _groupingSize <= 0) return; | 885 if (distanceFromEnd <= 1 || _groupingSize <= 0) return; |
434 if (distanceFromEnd == _finalGroupingSize + 1) { | 886 if (distanceFromEnd == _finalGroupingSize + 1) { |
435 _add(symbols.GROUP_SEP); | 887 _add(symbols.GROUP_SEP); |
436 } else if ((distanceFromEnd > _finalGroupingSize) && | 888 } else if ((distanceFromEnd > _finalGroupingSize) && |
437 (distanceFromEnd - _finalGroupingSize) % _groupingSize == 1) { | 889 (distanceFromEnd - _finalGroupingSize) % _groupingSize == 1) { |
438 _add(symbols.GROUP_SEP); | 890 _add(symbols.GROUP_SEP); |
439 } | 891 } |
440 } | 892 } |
441 | 893 |
442 /** Returns the code point for the character '0'. */ | 894 /// The code point for the character '0'. |
443 final _zero = '0'.codeUnits.first; | 895 static const _zero = 48; |
444 | 896 |
445 /** Returns the code point for the locale's zero digit. */ | 897 /// The code point for the locale's zero digit. |
446 // Note that there is a slight risk of a locale's zero digit not fitting | 898 /// |
447 // into a single code unit, but it seems very unlikely, and if it did, | 899 /// Initialized when the locale is set. |
448 // there's a pretty good chance that our assumptions about being able to do | 900 int _localeZero = 0; |
449 // arithmetic on it would also be invalid. | |
450 get _localeZero => symbols.ZERO_DIGIT.codeUnits.first; | |
451 | 901 |
452 /** | 902 /// The difference between our zero and '0'. |
453 * Returns the prefix for [x] based on whether it's positive or negative. | 903 /// |
454 * In en_US this would be '' and '-' respectively. | 904 /// In other words, a constant _localeZero - _zero. Initialized when |
455 */ | 905 /// the locale is set. |
| 906 int _zeroOffset = 0; |
| 907 |
| 908 /// Returns the prefix for [x] based on whether it's positive or negative. |
| 909 /// In en_US this would be '' and '-' respectively. |
456 String _signPrefix(x) => x.isNegative ? _negativePrefix : _positivePrefix; | 910 String _signPrefix(x) => x.isNegative ? _negativePrefix : _positivePrefix; |
457 | 911 |
458 /** | 912 /// Returns the suffix for [x] based on wether it's positive or negative. |
459 * Returns the suffix for [x] based on wether it's positive or negative. | 913 /// In en_US there are no suffixes for positive or negative. |
460 * In en_US there are no suffixes for positive or negative. | |
461 */ | |
462 String _signSuffix(x) => x.isNegative ? _negativeSuffix : _positiveSuffix; | 914 String _signSuffix(x) => x.isNegative ? _negativeSuffix : _positiveSuffix; |
463 | 915 |
464 void _setPattern(String newPattern) { | 916 void _setPattern(String newPattern) { |
465 if (newPattern == null) return; | 917 if (newPattern == null) return; |
466 // Make spaces non-breaking | 918 // Make spaces non-breaking |
467 _pattern = newPattern.replaceAll(' ', '\u00a0'); | 919 _pattern = newPattern.replaceAll(' ', '\u00a0'); |
468 var parser = new _NumberFormatParser(this, newPattern, currencyName); | 920 var parser = new _NumberFormatParser( |
| 921 this, newPattern, currencySymbol, decimalDigits); |
469 parser.parse(); | 922 parser.parse(); |
| 923 if (_overridesDecimalDigits) { |
| 924 _decimalDigits ??= _defaultDecimalDigits; |
| 925 minimumFractionDigits = _decimalDigits; |
| 926 maximumFractionDigits = _decimalDigits; |
| 927 } |
| 928 } |
| 929 |
| 930 /// Explicitly turn off any grouping (e.g. by thousands) in this format. |
| 931 /// |
| 932 /// This is used in compact number formatting, where we |
| 933 /// omit the normal grouping. Best to know what you're doing if you call it. |
| 934 void turnOffGrouping() { |
| 935 _groupingSize = 0; |
| 936 _finalGroupingSize = 0; |
470 } | 937 } |
471 | 938 |
472 String toString() => "NumberFormat($_locale, $_pattern)"; | 939 String toString() => "NumberFormat($_locale, $_pattern)"; |
473 } | 940 } |
474 | 941 |
475 /** | 942 /// A one-time object for parsing a particular numeric string. One-time here |
476 * A one-time object for parsing a particular numeric string. One-time here | 943 /// means an instance can only parse one string. This is implemented by |
477 * means an instance can only parse one string. This is implemented by | 944 /// transforming from a locale-specific format to one that the system can parse, |
478 * transforming from a locale-specific format to one that the system can parse, | 945 /// then calls the system parsing methods on it. |
479 * then calls the system parsing methods on it. | |
480 */ | |
481 class _NumberParser { | 946 class _NumberParser { |
482 | 947 /// The format for which we are parsing. |
483 /** The format for which we are parsing. */ | |
484 final NumberFormat format; | 948 final NumberFormat format; |
485 | 949 |
486 /** The text we are parsing. */ | 950 /// The text we are parsing. |
487 final String text; | 951 final String text; |
488 | 952 |
489 /** What we use to iterate over the input text. */ | 953 /// What we use to iterate over the input text. |
490 final _Stream input; | 954 final _Stream input; |
491 | 955 |
492 /** | 956 /// The result of parsing [text] according to [format]. Automatically |
493 * The result of parsing [text] according to [format]. Automatically | 957 /// populated in the constructor. |
494 * populated in the constructor. | |
495 */ | |
496 num value; | 958 num value; |
497 | 959 |
498 /** The symbols used by our format. */ | 960 /// The symbols used by our format. |
499 NumberSymbols get symbols => format.symbols; | 961 NumberSymbols get symbols => format.symbols; |
500 | 962 |
501 /** Where we accumulate the normalized representation of the number. */ | 963 /// Where we accumulate the normalized representation of the number. |
502 final StringBuffer _normalized = new StringBuffer(); | 964 final StringBuffer _normalized = new StringBuffer(); |
503 | 965 |
504 /** | 966 /// Did we see something that indicates this is, or at least might be, |
505 * Did we see something that indicates this is, or at least might be, | 967 /// a positive number. |
506 * a positive number. | |
507 */ | |
508 bool gotPositive = false; | 968 bool gotPositive = false; |
509 | 969 |
510 /** | 970 /// Did we see something that indicates this is, or at least might be, |
511 * Did we see something that indicates this is, or at least might be, | 971 /// a negative number. |
512 * a negative number. | |
513 */ | |
514 bool gotNegative = false; | 972 bool gotNegative = false; |
515 /** | 973 |
516 * Did we see the required positive suffix at the end. Should | 974 /// Did we see the required positive suffix at the end. Should |
517 * match [gotPositive]. | 975 /// match [gotPositive]. |
518 */ | |
519 bool gotPositiveSuffix = false; | 976 bool gotPositiveSuffix = false; |
520 /** | 977 |
521 * Did we see the required negative suffix at the end. Should | 978 /// Did we see the required negative suffix at the end. Should |
522 * match [gotNegative]. | 979 /// match [gotNegative]. |
523 */ | |
524 bool gotNegativeSuffix = false; | 980 bool gotNegativeSuffix = false; |
525 | 981 |
526 /** Should we stop parsing before hitting the end of the string. */ | 982 /// Should we stop parsing before hitting the end of the string. |
527 bool done = false; | 983 bool done = false; |
528 | 984 |
529 /** Have we already skipped over any required prefixes. */ | 985 /// Have we already skipped over any required prefixes. |
530 bool prefixesSkipped = false; | 986 bool prefixesSkipped = false; |
531 | 987 |
532 /** If the number is percent or permill, what do we divide by at the end. */ | 988 /// If the number is percent or permill, what do we divide by at the end. |
533 int scale = 1; | 989 int scale = 1; |
534 | 990 |
535 String get _positivePrefix => format._positivePrefix; | 991 String get _positivePrefix => format._positivePrefix; |
536 String get _negativePrefix => format._negativePrefix; | 992 String get _negativePrefix => format._negativePrefix; |
537 String get _positiveSuffix => format._positiveSuffix; | 993 String get _positiveSuffix => format._positiveSuffix; |
538 String get _negativeSuffix => format._negativeSuffix; | 994 String get _negativeSuffix => format._negativeSuffix; |
539 int get _zero => format._zero; | 995 int get _zero => NumberFormat._zero; |
540 int get _localeZero => format._localeZero; | 996 int get _localeZero => format._localeZero; |
541 | 997 |
542 /** | 998 /// Create a new [_NumberParser] on which we can call parse(). |
543 * Create a new [_NumberParser] on which we can call parse(). | |
544 */ | |
545 _NumberParser(this.format, text) | 999 _NumberParser(this.format, text) |
546 : this.text = text, | 1000 : this.text = text, |
547 this.input = new _Stream(text) { | 1001 this.input = new _Stream(text) { |
| 1002 scale = format._internalMultiplier; |
548 value = parse(); | 1003 value = parse(); |
549 } | 1004 } |
550 | 1005 |
551 /** | 1006 /// The strings we might replace with functions that return the replacement |
552 * The strings we might replace with functions that return the replacement | 1007 /// values. They are functions because we might need to check something |
553 * values. They are functions because we might need to check something | 1008 /// in the context. Note that the ordering is important here. For example, |
554 * in the context. Note that the ordering is important here. For example, | 1009 /// [symbols.PERCENT] might be " %", and we must handle that before we |
555 * [symbols.PERCENT] might be " %", and we must handle that before we | 1010 /// look at an individual space. |
556 * look at an individual space. | 1011 Map<String, Function> get replacements => |
557 */ | 1012 _replacements ??= _initializeReplacements(); |
558 Map<String, Function> get replacements => _replacements == null | |
559 ? _replacements = _initializeReplacements() | |
560 : _replacements; | |
561 | 1013 |
562 var _replacements; | 1014 Map<String, Function> _replacements; |
563 | 1015 |
564 Map _initializeReplacements() => { | 1016 Map<String, Function> _initializeReplacements() => { |
565 symbols.DECIMAL_SEP: () => '.', | 1017 symbols.DECIMAL_SEP: () => '.', |
566 symbols.EXP_SYMBOL: () => 'E', | 1018 symbols.EXP_SYMBOL: () => 'E', |
567 symbols.GROUP_SEP: handleSpace, | 1019 symbols.GROUP_SEP: handleSpace, |
568 symbols.PERCENT: () { | 1020 symbols.PERCENT: () { |
569 scale = _NumberFormatParser._PERCENT_SCALE; | 1021 scale = _NumberFormatParser._PERCENT_SCALE; |
570 return ''; | 1022 return ''; |
571 }, | 1023 }, |
572 symbols.PERMILL: () { | 1024 symbols.PERMILL: () { |
573 scale = _NumberFormatParser._PER_MILLE_SCALE; | 1025 scale = _NumberFormatParser._PER_MILLE_SCALE; |
574 return ''; | 1026 return ''; |
575 }, | 1027 }, |
576 ' ': handleSpace, | 1028 ' ': handleSpace, |
577 '\u00a0': handleSpace, | 1029 '\u00a0': handleSpace, |
578 '+': () => '+', | 1030 '+': () => '+', |
579 '-': () => '-', | 1031 '-': () => '-', |
580 }; | 1032 }; |
581 | 1033 |
582 invalidFormat() => | 1034 invalidFormat() => |
583 throw new FormatException("Invalid number: ${input.contents}"); | 1035 throw new FormatException("Invalid number: ${input.contents}"); |
584 | 1036 |
585 /** | 1037 /// Replace a space in the number with the normalized form. If space is not |
586 * Replace a space in the number with the normalized form. If space is not | 1038 /// a significant character (normally grouping) then it's just invalid. If it |
587 * a significant character (normally grouping) then it's just invalid. If it | 1039 /// is the grouping character, then it's only valid if it's followed by a |
588 * is the grouping character, then it's only valid if it's followed by a | 1040 /// digit. e.g. '$12 345.00' |
589 * digit. e.g. '$12 345.00' | |
590 */ | |
591 handleSpace() => | 1041 handleSpace() => |
592 groupingIsNotASpaceOrElseItIsSpaceFollowedByADigit ? '' : invalidFormat(); | 1042 groupingIsNotASpaceOrElseItIsSpaceFollowedByADigit ? '' : invalidFormat(); |
593 | 1043 |
594 /** | 1044 /// Determine if a space is a valid character in the number. See |
595 * Determine if a space is a valid character in the number. See [handleSpace]. | 1045 /// [handleSpace]. |
596 */ | |
597 bool get groupingIsNotASpaceOrElseItIsSpaceFollowedByADigit { | 1046 bool get groupingIsNotASpaceOrElseItIsSpaceFollowedByADigit { |
598 if (symbols.GROUP_SEP != '\u00a0' || symbols.GROUP_SEP != ' ') return true; | 1047 if (symbols.GROUP_SEP != '\u00a0' || symbols.GROUP_SEP != ' ') return true; |
599 var peeked = input.peek(symbols.GROUP_SEP.length + 1); | 1048 var peeked = input.peek(symbols.GROUP_SEP.length + 1); |
600 return asDigit(peeked[peeked.length - 1]) != null; | 1049 return asDigit(peeked[peeked.length - 1]) != null; |
601 } | 1050 } |
602 | 1051 |
603 /** | 1052 /// Turn [char] into a number representing a digit, or null if it doesn't |
604 * Turn [char] into a number representing a digit, or null if it doesn't | 1053 /// represent a digit in this locale. |
605 * represent a digit in this locale. | |
606 */ | |
607 int asDigit(String char) { | 1054 int asDigit(String char) { |
608 var charCode = char.codeUnitAt(0); | 1055 var charCode = char.codeUnitAt(0); |
609 var digitValue = charCode - _localeZero; | 1056 var digitValue = charCode - _localeZero; |
610 if (digitValue >= 0 && digitValue < 10) { | 1057 if (digitValue >= 0 && digitValue < 10) { |
611 return digitValue; | 1058 return digitValue; |
612 } else { | 1059 } else { |
613 return null; | 1060 return null; |
614 } | 1061 } |
615 } | 1062 } |
616 | 1063 |
617 /** | 1064 /// Check to see if the input begins with either the positive or negative |
618 * Check to see if the input begins with either the positive or negative | 1065 /// prefixes. Set the [gotPositive] and [gotNegative] variables accordingly. |
619 * prefixes. Set the [gotPositive] and [gotNegative] variables accordingly. | |
620 */ | |
621 void checkPrefixes({bool skip: false}) { | 1066 void checkPrefixes({bool skip: false}) { |
622 bool checkPrefix(String prefix, skip) { | 1067 bool checkPrefix(String prefix) => |
623 var matched = prefix.isNotEmpty && input.startsWith(prefix); | 1068 prefix.isNotEmpty && input.startsWith(prefix); |
624 if (skip && matched) input.read(prefix.length); | |
625 return matched; | |
626 } | |
627 | 1069 |
628 // TODO(alanknight): There's a faint possibility of a bug here where | 1070 // TODO(alanknight): There's a faint possibility of a bug here where |
629 // a positive prefix is followed by a negative prefix that's also a valid | 1071 // a positive prefix is followed by a negative prefix that's also a valid |
630 // part of the number, but that seems very unlikely. | 1072 // part of the number, but that seems very unlikely. |
631 if (checkPrefix(_positivePrefix, skip)) gotPositive = true; | 1073 if (checkPrefix(_positivePrefix)) gotPositive = true; |
632 if (checkPrefix(_negativePrefix, skip)) gotNegative = true; | 1074 if (checkPrefix(_negativePrefix)) gotNegative = true; |
633 | 1075 |
634 // Copied from Closure. It doesn't seem to be necessary to pass the test | 1076 // The positive prefix might be a substring of the negative, in |
635 // suite, so I'm not sure it's really needed. | 1077 // which case both would match. |
636 if (gotPositive && gotNegative) { | 1078 if (gotPositive && gotNegative) { |
637 if (_positivePrefix.length > _negativePrefix.length) { | 1079 if (_positivePrefix.length > _negativePrefix.length) { |
638 gotNegative = false; | 1080 gotNegative = false; |
639 } else if (_negativePrefix.length > _positivePrefix.length) { | 1081 } else if (_negativePrefix.length > _positivePrefix.length) { |
640 gotPositive = false; | 1082 gotPositive = false; |
641 } | 1083 } |
642 } | 1084 } |
| 1085 if (skip) { |
| 1086 if (gotPositive) input.read(_positivePrefix.length); |
| 1087 if (gotNegative) input.read(_negativePrefix.length); |
| 1088 } |
643 } | 1089 } |
644 | 1090 |
645 /** | 1091 /// If the rest of our input is either the positive or negative suffix, |
646 * If the rest of our input is either the positive or negative suffix, | 1092 /// set [gotPositiveSuffix] or [gotNegativeSuffix] accordingly. |
647 * set [gotPositiveSuffix] or [gotNegativeSuffix] accordingly. | |
648 */ | |
649 void checkSuffixes() { | 1093 void checkSuffixes() { |
650 var remainder = input.rest(); | 1094 var remainder = input.rest(); |
651 if (remainder == _positiveSuffix) gotPositiveSuffix = true; | 1095 if (remainder == _positiveSuffix) gotPositiveSuffix = true; |
652 if (remainder == _negativeSuffix) gotNegativeSuffix = true; | 1096 if (remainder == _negativeSuffix) gotNegativeSuffix = true; |
653 } | 1097 } |
654 | 1098 |
655 /** | 1099 /// We've encountered a character that's not a digit. Go through our |
656 * We've encountered a character that's not a digit. Go through our | 1100 /// replacement rules looking for how to handle it. If we see something |
657 * replacement rules looking for how to handle it. If we see something | 1101 /// that's not a digit and doesn't have a replacement, then we're done |
658 * that's not a digit and doesn't have a replacement, then we're done | 1102 /// and the number is probably invalid. |
659 * and the number is probably invalid. | |
660 */ | |
661 void processNonDigit() { | 1103 void processNonDigit() { |
| 1104 // It might just be a prefix that we haven't skipped. We don't want to |
| 1105 // skip them initially because they might also be semantically meaningful, |
| 1106 // e.g. leading %. So we allow them through the loop, but only once. |
| 1107 var foundAnInterpretation = false; |
| 1108 if (input.index == 0 && !prefixesSkipped) { |
| 1109 prefixesSkipped = true; |
| 1110 checkPrefixes(skip: true); |
| 1111 foundAnInterpretation = true; |
| 1112 } |
| 1113 |
662 for (var key in replacements.keys) { | 1114 for (var key in replacements.keys) { |
663 if (input.startsWith(key)) { | 1115 if (input.startsWith(key)) { |
664 _normalized.write(replacements[key]()); | 1116 _normalized.write(replacements[key]()); |
665 input.read(key.length); | 1117 input.read(key.length); |
666 return; | 1118 return; |
667 } | 1119 } |
668 } | 1120 } |
669 // It might just be a prefix that we haven't skipped. We don't want to | 1121 // We haven't found either of these things, this seems invalid. |
670 // skip them initially because they might also be semantically meaningful, | 1122 if (!foundAnInterpretation) { |
671 // e.g. leading %. So we allow them through the loop, but only once. | |
672 if (input.index == 0 && !prefixesSkipped) { | |
673 prefixesSkipped = true; | |
674 checkPrefixes(skip: true); | |
675 } else { | |
676 done = true; | 1123 done = true; |
677 } | 1124 } |
678 } | 1125 } |
679 | 1126 |
680 /** | 1127 /// Parse [text] and return the resulting number. Throws [FormatException] |
681 * Parse [text] and return the resulting number. Throws [FormatException] | 1128 /// if we can't parse it. |
682 * if we can't parse it. | |
683 */ | |
684 num parse() { | 1129 num parse() { |
685 if (text == symbols.NAN) return double.NAN; | 1130 if (text == symbols.NAN) return double.NAN; |
686 if (text == "$_positivePrefix${symbols.INFINITY}$_positiveSuffix") { | 1131 if (text == "$_positivePrefix${symbols.INFINITY}$_positiveSuffix") { |
687 return double.INFINITY; | 1132 return double.INFINITY; |
688 } | 1133 } |
689 if (text == "$_negativePrefix${symbols.INFINITY}$_negativeSuffix") { | 1134 if (text == "$_negativePrefix${symbols.INFINITY}$_negativeSuffix") { |
690 return double.NEGATIVE_INFINITY; | 1135 return double.NEGATIVE_INFINITY; |
691 } | 1136 } |
692 | 1137 |
693 checkPrefixes(); | 1138 checkPrefixes(); |
694 var parsed = parseNumber(input); | 1139 var parsed = parseNumber(input); |
695 | 1140 |
696 if (gotPositive && !gotPositiveSuffix) invalidNumber(); | 1141 if (gotPositive && !gotPositiveSuffix) invalidNumber(); |
697 if (gotNegative && !gotNegativeSuffix) invalidNumber(); | 1142 if (gotNegative && !gotNegativeSuffix) invalidNumber(); |
698 if (!input.atEnd()) invalidNumber(); | 1143 if (!input.atEnd()) invalidNumber(); |
699 | 1144 |
700 return parsed; | 1145 return parsed; |
701 } | 1146 } |
702 | 1147 |
703 /** The number is invalid, throw a [FormatException]. */ | 1148 /// The number is invalid, throw a [FormatException]. |
704 void invalidNumber() => | 1149 void invalidNumber() => |
705 throw new FormatException("Invalid Number: ${input.contents}"); | 1150 throw new FormatException("Invalid Number: ${input.contents}"); |
706 | 1151 |
707 /** | 1152 /// Parse the number portion of the input, i.e. not any prefixes or suffixes, |
708 * Parse the number portion of the input, i.e. not any prefixes or suffixes, | 1153 /// and assuming NaN and Infinity are already handled. |
709 * and assuming NaN and Infinity are already handled. | |
710 */ | |
711 num parseNumber(_Stream input) { | 1154 num parseNumber(_Stream input) { |
| 1155 if (gotNegative) { |
| 1156 _normalized.write('-'); |
| 1157 } |
712 while (!done && !input.atEnd()) { | 1158 while (!done && !input.atEnd()) { |
713 int digit = asDigit(input.peek()); | 1159 int digit = asDigit(input.peek()); |
714 if (digit != null) { | 1160 if (digit != null) { |
715 _normalized.writeCharCode(_zero + digit); | 1161 _normalized.writeCharCode(_zero + digit); |
716 input.next(); | 1162 input.next(); |
717 } else { | 1163 } else { |
718 processNonDigit(); | 1164 processNonDigit(); |
719 } | 1165 } |
720 checkSuffixes(); | 1166 checkSuffixes(); |
721 } | 1167 } |
722 | 1168 |
723 var normalizedText = _normalized.toString(); | 1169 var normalizedText = _normalized.toString(); |
724 num parsed = int.parse(normalizedText, onError: (message) => null); | 1170 num parsed = int.parse(normalizedText, onError: (message) => null); |
725 if (parsed == null) parsed = double.parse(normalizedText); | 1171 if (parsed == null) parsed = double.parse(normalizedText); |
726 return parsed / scale; | 1172 return parsed / scale; |
727 } | 1173 } |
728 } | 1174 } |
729 | 1175 |
730 /** | 1176 /// Private class that parses the numeric formatting pattern and sets the |
731 * Private class that parses the numeric formatting pattern and sets the | 1177 /// variables in [format] to appropriate values. Instances of this are |
732 * variables in [format] to appropriate values. Instances of this are | 1178 /// transient and store parsing state in instance variables, so can only be used |
733 * transient and store parsing state in instance variables, so can only be used | 1179 /// to parse a single pattern. |
734 * to parse a single pattern. | |
735 */ | |
736 class _NumberFormatParser { | 1180 class _NumberFormatParser { |
737 | 1181 /// The special characters in the pattern language. All others are treated |
738 /** | 1182 /// as literals. |
739 * The special characters in the pattern language. All others are treated | |
740 * as literals. | |
741 */ | |
742 static const _PATTERN_SEPARATOR = ';'; | 1183 static const _PATTERN_SEPARATOR = ';'; |
743 static const _QUOTE = "'"; | 1184 static const _QUOTE = "'"; |
744 static const _PATTERN_DIGIT = '#'; | 1185 static const _PATTERN_DIGIT = '#'; |
745 static const _PATTERN_ZERO_DIGIT = '0'; | 1186 static const _PATTERN_ZERO_DIGIT = '0'; |
746 static const _PATTERN_GROUPING_SEPARATOR = ','; | 1187 static const _PATTERN_GROUPING_SEPARATOR = ','; |
747 static const _PATTERN_DECIMAL_SEPARATOR = '.'; | 1188 static const _PATTERN_DECIMAL_SEPARATOR = '.'; |
748 static const _PATTERN_CURRENCY_SIGN = '\u00A4'; | 1189 static const _PATTERN_CURRENCY_SIGN = '\u00A4'; |
749 static const _PATTERN_PER_MILLE = '\u2030'; | 1190 static const _PATTERN_PER_MILLE = '\u2030'; |
750 static const _PER_MILLE_SCALE = 1000; | 1191 static const _PER_MILLE_SCALE = 1000; |
751 static const _PATTERN_PERCENT = '%'; | 1192 static const _PATTERN_PERCENT = '%'; |
752 static const _PERCENT_SCALE = 100; | 1193 static const _PERCENT_SCALE = 100; |
753 static const _PATTERN_EXPONENT = 'E'; | 1194 static const _PATTERN_EXPONENT = 'E'; |
754 static const _PATTERN_PLUS = '+'; | 1195 static const _PATTERN_PLUS = '+'; |
755 | 1196 |
756 /** The format whose state we are setting. */ | 1197 /// The format whose state we are setting. |
757 final NumberFormat format; | 1198 final NumberFormat format; |
758 | 1199 |
759 /** The pattern we are parsing. */ | 1200 /// The pattern we are parsing. |
760 final _StringIterator pattern; | 1201 final _StringIterator pattern; |
761 | 1202 |
762 /** We can be passed a specific currency symbol, regardless of the locale. */ | 1203 /// We can be passed a specific currency symbol, regardless of the locale. |
763 String currencyName; | 1204 String currencySymbol; |
764 | 1205 |
765 /** | 1206 /// We can be given a specific number of decimal places, overriding the |
766 * Create a new [_NumberFormatParser] for a particular [NumberFormat] and | 1207 /// default. |
767 * [input] pattern. | 1208 final int decimalDigits; |
768 */ | 1209 |
769 _NumberFormatParser(this.format, input, this.currencyName) | 1210 /// Create a new [_NumberFormatParser] for a particular [NumberFormat] and |
| 1211 /// [input] pattern. |
| 1212 _NumberFormatParser( |
| 1213 this.format, input, this.currencySymbol, this.decimalDigits) |
770 : pattern = _iterator(input) { | 1214 : pattern = _iterator(input) { |
771 pattern.moveNext(); | 1215 pattern.moveNext(); |
772 } | 1216 } |
773 | 1217 |
774 /** The [NumberSymbols] for the locale in which our [format] prints. */ | 1218 /// The [NumberSymbols] for the locale in which our [format] prints. |
775 NumberSymbols get symbols => format.symbols; | 1219 NumberSymbols get symbols => format.symbols; |
776 | 1220 |
777 /** Parse the input pattern and set the values. */ | 1221 /// Parse the input pattern and set the values. |
778 void parse() { | 1222 void parse() { |
779 format._positivePrefix = _parseAffix(); | 1223 format._positivePrefix = _parseAffix(); |
780 var trunk = _parseTrunk(); | 1224 var trunk = _parseTrunk(); |
781 format._positiveSuffix = _parseAffix(); | 1225 format._positiveSuffix = _parseAffix(); |
782 // If we have separate positive and negative patterns, now parse the | 1226 // If we have separate positive and negative patterns, now parse the |
783 // the negative version. | 1227 // the negative version. |
784 if (pattern.current == _NumberFormatParser._PATTERN_SEPARATOR) { | 1228 if (pattern.current == _NumberFormatParser._PATTERN_SEPARATOR) { |
785 pattern.moveNext(); | 1229 pattern.moveNext(); |
786 format._negativePrefix = _parseAffix(); | 1230 format._negativePrefix = _parseAffix(); |
787 // Skip over the negative trunk, verifying that it's identical to the | 1231 // Skip over the negative trunk, verifying that it's identical to the |
788 // positive trunk. | 1232 // positive trunk. |
789 for (var each in _iterable(trunk)) { | 1233 for (var each in _iterable(trunk)) { |
790 if (pattern.current != each && pattern.current != null) { | 1234 if (pattern.current != each && pattern.current != null) { |
791 throw new FormatException( | 1235 throw new FormatException( |
792 "Positive and negative trunks must be the same"); | 1236 "Positive and negative trunks must be the same"); |
793 } | 1237 } |
794 pattern.moveNext(); | 1238 pattern.moveNext(); |
795 } | 1239 } |
796 format._negativeSuffix = _parseAffix(); | 1240 format._negativeSuffix = _parseAffix(); |
797 } else { | 1241 } else { |
798 // If no negative affix is specified, they share the same positive affix. | 1242 // If no negative affix is specified, they share the same positive affix. |
799 format._negativePrefix = format._negativePrefix + format._positivePrefix; | 1243 format._negativePrefix = format._negativePrefix + format._positivePrefix; |
800 format._negativeSuffix = format._positiveSuffix + format._negativeSuffix; | 1244 format._negativeSuffix = format._positiveSuffix + format._negativeSuffix; |
801 } | 1245 } |
802 } | 1246 } |
803 | 1247 |
804 /** | 1248 /// Variable used in parsing prefixes and suffixes to keep track of |
805 * Variable used in parsing prefixes and suffixes to keep track of | 1249 /// whether or not we are in a quoted region. |
806 * whether or not we are in a quoted region. | |
807 */ | |
808 bool inQuote = false; | 1250 bool inQuote = false; |
809 | 1251 |
810 /** | 1252 /// Parse a prefix or suffix and return the prefix/suffix string. Note that |
811 * Parse a prefix or suffix and return the prefix/suffix string. Note that | 1253 /// this also may modify the state of [format]. |
812 * this also may modify the state of [format]. | |
813 */ | |
814 String _parseAffix() { | 1254 String _parseAffix() { |
815 var affix = new StringBuffer(); | 1255 var affix = new StringBuffer(); |
816 inQuote = false; | 1256 inQuote = false; |
817 while (parseCharacterAffix(affix) && pattern.moveNext()); | 1257 while (parseCharacterAffix(affix) && pattern.moveNext()); |
818 return affix.toString(); | 1258 return affix.toString(); |
819 } | 1259 } |
820 | 1260 |
821 /** | 1261 /// Parse an individual character as part of a prefix or suffix. Return true |
822 * Parse an individual character as part of a prefix or suffix. Return true | 1262 /// if we should continue to look for more affix characters, and false if |
823 * if we should continue to look for more affix characters, and false if | 1263 /// we have reached the end. |
824 * we have reached the end. | |
825 */ | |
826 bool parseCharacterAffix(StringBuffer affix) { | 1264 bool parseCharacterAffix(StringBuffer affix) { |
827 var ch = pattern.current; | 1265 var ch = pattern.current; |
828 if (ch == null) return false; | 1266 if (ch == null) return false; |
829 if (ch == _QUOTE) { | 1267 if (ch == _QUOTE) { |
830 if (pattern.peek == _QUOTE) { | 1268 if (pattern.peek == _QUOTE) { |
831 pattern.moveNext(); | 1269 pattern.moveNext(); |
832 affix.write(_QUOTE); // 'don''t' | 1270 affix.write(_QUOTE); // 'don''t' |
833 } else { | 1271 } else { |
834 inQuote = !inQuote; | 1272 inQuote = !inQuote; |
835 } | 1273 } |
836 return true; | 1274 return true; |
837 } | 1275 } |
838 | 1276 |
839 if (inQuote) { | 1277 if (inQuote) { |
840 affix.write(ch); | 1278 affix.write(ch); |
841 } else { | 1279 } else { |
842 switch (ch) { | 1280 switch (ch) { |
843 case _PATTERN_DIGIT: | 1281 case _PATTERN_DIGIT: |
844 case _PATTERN_ZERO_DIGIT: | 1282 case _PATTERN_ZERO_DIGIT: |
845 case _PATTERN_GROUPING_SEPARATOR: | 1283 case _PATTERN_GROUPING_SEPARATOR: |
846 case _PATTERN_DECIMAL_SEPARATOR: | 1284 case _PATTERN_DECIMAL_SEPARATOR: |
847 case _PATTERN_SEPARATOR: | 1285 case _PATTERN_SEPARATOR: |
848 return false; | 1286 return false; |
849 case _PATTERN_CURRENCY_SIGN: | 1287 case _PATTERN_CURRENCY_SIGN: |
850 // TODO(alanknight): Handle the local/global/portable currency signs | 1288 // TODO(alanknight): Handle the local/global/portable currency signs |
851 affix.write(currencyName); | 1289 affix.write(currencySymbol); |
852 break; | 1290 break; |
853 case _PATTERN_PERCENT: | 1291 case _PATTERN_PERCENT: |
854 if (format._multiplier != 1 && format._multiplier != _PERCENT_SCALE) { | 1292 if (format._multiplier != 1 && format._multiplier != _PERCENT_SCALE) { |
855 throw new FormatException('Too many percent/permill'); | 1293 throw new FormatException('Too many percent/permill'); |
856 } | 1294 } |
857 format._multiplier = _PERCENT_SCALE; | 1295 format._multiplier = _PERCENT_SCALE; |
858 affix.write(symbols.PERCENT); | 1296 affix.write(symbols.PERCENT); |
859 break; | 1297 break; |
860 case _PATTERN_PER_MILLE: | 1298 case _PATTERN_PER_MILLE: |
861 if (format._multiplier != 1 && | 1299 if (format._multiplier != 1 && |
862 format._multiplier != _PER_MILLE_SCALE) { | 1300 format._multiplier != _PER_MILLE_SCALE) { |
863 throw new FormatException('Too many percent/permill'); | 1301 throw new FormatException('Too many percent/permill'); |
864 } | 1302 } |
865 format._multiplier = _PER_MILLE_SCALE; | 1303 format._multiplier = _PER_MILLE_SCALE; |
866 affix.write(symbols.PERMILL); | 1304 affix.write(symbols.PERMILL); |
867 break; | 1305 break; |
868 default: | 1306 default: |
869 affix.write(ch); | 1307 affix.write(ch); |
870 } | 1308 } |
871 } | 1309 } |
872 return true; | 1310 return true; |
873 } | 1311 } |
874 | 1312 |
875 /** Variables used in [_parseTrunk] and [parseTrunkCharacter]. */ | 1313 /// Variables used in [_parseTrunk] and [parseTrunkCharacter]. |
876 var decimalPos = -1; | 1314 var decimalPos = -1; |
877 var digitLeftCount = 0; | 1315 var digitLeftCount = 0; |
878 var zeroDigitCount = 0; | 1316 var zeroDigitCount = 0; |
879 var digitRightCount = 0; | 1317 var digitRightCount = 0; |
880 var groupingCount = -1; | 1318 var groupingCount = -1; |
881 | 1319 |
882 /** | 1320 /// Parse the "trunk" portion of the pattern, the piece that doesn't include |
883 * Parse the "trunk" portion of the pattern, the piece that doesn't include | 1321 /// positive or negative prefixes or suffixes. |
884 * positive or negative prefixes or suffixes. | |
885 */ | |
886 String _parseTrunk() { | 1322 String _parseTrunk() { |
887 var loop = true; | 1323 var loop = true; |
888 var trunk = new StringBuffer(); | 1324 var trunk = new StringBuffer(); |
889 while (pattern.current != null && loop) { | 1325 while (pattern.current != null && loop) { |
890 loop = parseTrunkCharacter(trunk); | 1326 loop = parseTrunkCharacter(trunk); |
891 } | 1327 } |
892 | 1328 |
893 if (zeroDigitCount == 0 && digitLeftCount > 0 && decimalPos >= 0) { | 1329 if (zeroDigitCount == 0 && digitLeftCount > 0 && decimalPos >= 0) { |
894 // Handle '###.###' and '###.' and '.###' | 1330 // Handle '###.###' and '###.' and '.###' |
895 // Handle '.###' | 1331 // Handle '.###' |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
938 format._finalGroupingSize = max(0, groupingCount); | 1374 format._finalGroupingSize = max(0, groupingCount); |
939 if (!format._groupingSizeSetExplicitly) { | 1375 if (!format._groupingSizeSetExplicitly) { |
940 format._groupingSize = format._finalGroupingSize; | 1376 format._groupingSize = format._finalGroupingSize; |
941 } | 1377 } |
942 format._decimalSeparatorAlwaysShown = | 1378 format._decimalSeparatorAlwaysShown = |
943 decimalPos == 0 || decimalPos == totalDigits; | 1379 decimalPos == 0 || decimalPos == totalDigits; |
944 | 1380 |
945 return trunk.toString(); | 1381 return trunk.toString(); |
946 } | 1382 } |
947 | 1383 |
948 /** | 1384 /// Parse an individual character of the trunk. Return true if we should |
949 * Parse an individual character of the trunk. Return true if we should | 1385 /// continue to look for additional trunk characters or false if we have |
950 * continue to look for additional trunk characters or false if we have | 1386 /// reached the end. |
951 * reached the end. | |
952 */ | |
953 bool parseTrunkCharacter(trunk) { | 1387 bool parseTrunkCharacter(trunk) { |
954 var ch = pattern.current; | 1388 var ch = pattern.current; |
955 switch (ch) { | 1389 switch (ch) { |
956 case _PATTERN_DIGIT: | 1390 case _PATTERN_DIGIT: |
957 if (zeroDigitCount > 0) { | 1391 if (zeroDigitCount > 0) { |
958 digitRightCount++; | 1392 digitRightCount++; |
959 } else { | 1393 } else { |
960 digitLeftCount++; | 1394 digitLeftCount++; |
961 } | 1395 } |
962 if (groupingCount >= 0 && decimalPos < 0) { | 1396 if (groupingCount >= 0 && decimalPos < 0) { |
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1020 return false; | 1454 return false; |
1021 default: | 1455 default: |
1022 return false; | 1456 return false; |
1023 } | 1457 } |
1024 trunk.write(ch); | 1458 trunk.write(ch); |
1025 pattern.moveNext(); | 1459 pattern.moveNext(); |
1026 return true; | 1460 return true; |
1027 } | 1461 } |
1028 } | 1462 } |
1029 | 1463 |
1030 /** | 1464 /// Returns an [Iterable] on the string as a list of substrings. |
1031 * Returns an [Iterable] on the string as a list of substrings. | |
1032 */ | |
1033 Iterable _iterable(String s) => new _StringIterable(s); | 1465 Iterable _iterable(String s) => new _StringIterable(s); |
1034 | 1466 |
1035 /** | 1467 /// Return an iterator on the string as a list of substrings. |
1036 * Return an iterator on the string as a list of substrings. | 1468 Iterator<String> _iterator(String s) => new _StringIterator(s); |
1037 */ | |
1038 Iterator _iterator(String s) => new _StringIterator(s); | |
1039 | 1469 |
1040 // TODO(nweiz): remove this when issue 3780 is fixed. | 1470 // TODO(nweiz): remove this when issue 3780 is fixed. |
1041 /** | 1471 /// Provides an Iterable that wraps [_iterator] so it can be used in a `for` |
1042 * Provides an Iterable that wraps [_iterator] so it can be used in a `for` | 1472 /// loop. |
1043 * loop. | |
1044 */ | |
1045 class _StringIterable extends IterableBase<String> { | 1473 class _StringIterable extends IterableBase<String> { |
1046 final Iterator<String> iterator; | 1474 final Iterator<String> iterator; |
1047 | 1475 |
1048 _StringIterable(String s) : iterator = _iterator(s); | 1476 _StringIterable(String s) : iterator = _iterator(s); |
1049 } | 1477 } |
1050 | 1478 |
1051 /** | 1479 /// Provides an iterator over a string as a list of substrings, and also |
1052 * Provides an iterator over a string as a list of substrings, and also | 1480 /// gives us a lookahead of one via the [peek] method. |
1053 * gives us a lookahead of one via the [peek] method. | |
1054 */ | |
1055 class _StringIterator implements Iterator<String> { | 1481 class _StringIterator implements Iterator<String> { |
1056 final String input; | 1482 final String input; |
1057 int nextIndex = 0; | 1483 int nextIndex = 0; |
1058 String _current = null; | 1484 String _current = null; |
1059 | 1485 |
1060 _StringIterator(input) : input = _validate(input); | 1486 _StringIterator(input) : input = _validate(input); |
1061 | 1487 |
1062 String get current => _current; | 1488 String get current => _current; |
1063 | 1489 |
1064 bool moveNext() { | 1490 bool moveNext() { |
1065 if (nextIndex >= input.length) { | 1491 if (nextIndex >= input.length) { |
1066 _current = null; | 1492 _current = null; |
1067 return false; | 1493 return false; |
1068 } | 1494 } |
1069 _current = input[nextIndex++]; | 1495 _current = input[nextIndex++]; |
1070 return true; | 1496 return true; |
1071 } | 1497 } |
1072 | 1498 |
1073 String get peek => nextIndex >= input.length ? null : input[nextIndex]; | 1499 String get peek => nextIndex >= input.length ? null : input[nextIndex]; |
1074 | 1500 |
1075 Iterator<String> get iterator => this; | 1501 Iterator<String> get iterator => this; |
1076 | 1502 |
1077 static String _validate(input) { | 1503 static String _validate(input) { |
1078 if (input is! String) throw new ArgumentError(input); | 1504 if (input is! String) throw new ArgumentError(input); |
1079 return input; | 1505 return input; |
1080 } | 1506 } |
1081 } | 1507 } |
| 1508 |
| 1509 /// Used primarily for currency formatting, this number-like class stores |
| 1510 /// millionths of a currency unit, typically as an Int64. |
| 1511 /// |
| 1512 /// It supports no operations other than being used for Intl number formatting. |
| 1513 abstract class MicroMoney { |
| 1514 factory MicroMoney(micros) => new _MicroMoney(micros); |
| 1515 } |
| 1516 |
| 1517 /// Used primarily for currency formatting, this stores millionths of a |
| 1518 /// currency unit, typically as an Int64. |
| 1519 /// |
| 1520 /// This private class provides the operations needed by the formatting code. |
| 1521 class _MicroMoney implements MicroMoney { |
| 1522 var _micros; |
| 1523 _MicroMoney(this._micros); |
| 1524 static const _multiplier = 1000000; |
| 1525 |
| 1526 get _integerPart => _micros ~/ _multiplier; |
| 1527 int get _fractionPart => (this - _integerPart)._micros.toInt().abs(); |
| 1528 |
| 1529 bool get isNegative => _micros.isNegative; |
| 1530 |
| 1531 _MicroMoney abs() => isNegative ? new _MicroMoney(_micros.abs()) : this; |
| 1532 |
| 1533 // Note that if this is done in a general way there's a risk of integer |
| 1534 // overflow on JS when multiplying out the [other] parameter, which may be |
| 1535 // an Int64. In formatting we only ever subtract out our own integer part. |
| 1536 _MicroMoney operator -(other) { |
| 1537 if (other is _MicroMoney) return new _MicroMoney(_micros - other._micros); |
| 1538 return new _MicroMoney(_micros - (other * _multiplier)); |
| 1539 } |
| 1540 |
| 1541 _MicroMoney operator +(other) { |
| 1542 if (other is _MicroMoney) return new _MicroMoney(_micros + other._micros); |
| 1543 return new _MicroMoney(_micros + (other * _multiplier)); |
| 1544 } |
| 1545 |
| 1546 _MicroMoney operator ~/(divisor) { |
| 1547 if (divisor is! int) { |
| 1548 throw new ArgumentError.value( |
| 1549 divisor, 'divisor', '_MicroMoney ~/ only supports int arguments.'); |
| 1550 } |
| 1551 return new _MicroMoney((_integerPart ~/ divisor) * _multiplier); |
| 1552 } |
| 1553 |
| 1554 _MicroMoney operator *(other) { |
| 1555 if (other is! int) { |
| 1556 throw new ArgumentError.value( |
| 1557 other, 'other', '_MicroMoney * only supports int arguments.'); |
| 1558 } |
| 1559 return new _MicroMoney( |
| 1560 (_integerPart * other) * _multiplier + (_fractionPart * other)); |
| 1561 } |
| 1562 |
| 1563 /// Note that this only really supports remainder from an int, |
| 1564 /// not division by another MicroMoney |
| 1565 _MicroMoney remainder(other) { |
| 1566 if (other is! int) { |
| 1567 throw new ArgumentError.value( |
| 1568 other, 'other', '_MicroMoney.remainder only supports int arguments.'); |
| 1569 } |
| 1570 return new _MicroMoney(_micros.remainder(other * _multiplier)); |
| 1571 } |
| 1572 |
| 1573 double toDouble() => _micros.toDouble() / _multiplier; |
| 1574 |
| 1575 int toInt() => _integerPart.toInt(); |
| 1576 |
| 1577 String toString() { |
| 1578 var beforeDecimal = _integerPart.toString(); |
| 1579 var decimalPart = ''; |
| 1580 var fractionPart = _fractionPart; |
| 1581 if (fractionPart != 0) { |
| 1582 decimalPart = '.' + fractionPart.toString(); |
| 1583 } |
| 1584 return '$beforeDecimal$decimalPart'; |
| 1585 } |
| 1586 } |
OLD | NEW |