Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(361)

Side by Side Diff: packages/intl/lib/src/intl/compact_number_format.dart

Issue 2989763002: Update charted to 0.4.8 and roll (Closed)
Patch Set: Removed Cutch from list of reviewers Created 3 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « packages/intl/lib/src/intl/bidi_utils.dart ('k') | packages/intl/lib/src/intl/date_format.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
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.
4
5 part of intl;
6
7 /// Represents a compact format for a particular base
8 ///
9 /// For example, 10k can be used to represent 10,000. Corresponds to one of the
10 /// patterns in COMPACT_DECIMAL_SHORT_FORMAT. So, for example, in en_US we have
11 /// the pattern
12 ///
13 /// 4: '0K'
14 /// which matches
15 ///
16 /// new _CompactStyle(pattern: '0K', requiredDigits: 4, divisor: 1000,
17 /// expectedDigits: 1, prefix: '', suffix: 'K');
18 ///
19 /// where expectedDigits is the number of zeros.
20 class _CompactStyle {
21 _CompactStyle(
22 {this.pattern,
23 this.requiredDigits: 0,
24 this.divisor: 1,
25 this.expectedDigits: 1,
26 this.prefix: '',
27 this.suffix: ''});
28
29 /// The pattern on which this is based.
30 ///
31 /// We don't actually need this, but it makes debugging easier.
32 String pattern;
33
34 /// The length for which the format applies.
35 ///
36 /// So if this is 3, we expect it to apply to numbers from 100 up. Typically
37 /// it would be from 100 to 1000, but that depends if there's a style for 4 or
38 /// not. This is the CLDR index of the pattern, and usually determines the
39 /// divisor, but if the pattern is just a 0 with no prefix or suffix then we
40 /// don't divide at all.
41 int requiredDigits;
42
43 /// What should we divide the number by in order to print. Normally is either
44 /// 10^requiredDigits or 1 if we shouldn't divide at all.
45 int divisor;
46
47 /// How many integer digits do we expect to print - the number of zeros in the
48 /// CLDR pattern.
49 int expectedDigits;
50
51 /// Text we put in front of the number part.
52 String prefix;
53
54 /// Text we put after the number part.
55 String suffix;
56
57 /// How many total digits do we expect in the number.
58 ///
59 /// If the pattern is
60 ///
61 /// 4: "00K",
62 ///
63 /// then this is 5, meaning we expect this to be a 5-digit (or more)
64 /// number. We will scale by 1000 and expect 2 integer digits remaining, so we
65 /// get something like '12K'. This is used to find the closest pattern for a
66 /// number.
67 get totalDigits => requiredDigits + expectedDigits - 1;
68
69 /// Return true if this is the fallback compact pattern, printing the number
70 /// un-compacted. e.g. 1200 might print as "1.2K", but 12 just prints as "12".
71 ///
72 /// For currencies, with the fallback pattern we use the super implementation
73 /// so that we will respect things like the default number of decimal digits
74 /// for a particular currency (e.g. two for USD, zero for JPY)
75 bool get isFallback => pattern == null || pattern == '0';
76
77 /// Should we print the number as-is, without dividing.
78 ///
79 /// This happens if the pattern has no abbreviation for scaling (e.g. K, M).
80 /// So either the pattern is empty or it is of a form like '0 $'. This is a
81 /// workaround for locales like "it", which include patterns with no suffix
82 /// for numbers >= 1000 but < 1,000,000.
83 bool get printsAsIs =>
84 isFallback ||
85 pattern.replaceAll(new RegExp('[0\u00a0\u00a4]'), '').isEmpty;
86 }
87
88 enum _CompactFormatType {
89 COMPACT_DECIMAL_SHORT_PATTERN,
90 COMPACT_DECIMAL_LONG_PATTERN,
91 COMPACT_DECIMAL_SHORT_CURRENCY_PATTERN
92 }
93
94 class _CompactNumberFormat extends NumberFormat {
95 /// A default, using the decimal pattern, for the [getPattern] constructor par ameter.
96 static String _forDecimal(NumberSymbols symbols) => symbols.DECIMAL_PATTERN;
97
98 // Will be either the COMPACT_DECIMAL_SHORT_PATTERN,
99 // COMPACT_DECIMAL_LONG_PATTERN, or COMPACT_DECIMAL_SHORT_CURRENCY_PATTERN
100 Map<int, String> _patterns;
101
102 List<_CompactStyle> _styles = [];
103
104 _CompactNumberFormat(
105 {String locale,
106 _CompactFormatType formatType,
107 String name,
108 String currencySymbol,
109 String getPattern(NumberSymbols symbols): _forDecimal,
110 String computeCurrencySymbol(NumberFormat),
111 int decimalDigits,
112 bool isForCurrency: false})
113 : super._forPattern(locale, getPattern,
114 name: name,
115 currencySymbol: currencySymbol,
116 computeCurrencySymbol: computeCurrencySymbol,
117 decimalDigits: decimalDigits,
118 isForCurrency: isForCurrency) {
119 significantDigits = 3;
120 turnOffGrouping();
121 switch (formatType) {
122 case _CompactFormatType.COMPACT_DECIMAL_SHORT_PATTERN:
123 _patterns = compactSymbols.COMPACT_DECIMAL_SHORT_PATTERN;
124 break;
125 case _CompactFormatType.COMPACT_DECIMAL_LONG_PATTERN:
126 _patterns = compactSymbols.COMPACT_DECIMAL_LONG_PATTERN ??
127 compactSymbols.COMPACT_DECIMAL_SHORT_PATTERN;
128 break;
129 case _CompactFormatType.COMPACT_DECIMAL_SHORT_CURRENCY_PATTERN:
130 _patterns = compactSymbols.COMPACT_DECIMAL_SHORT_CURRENCY_PATTERN;
131 break;
132 default:
133 throw new ArgumentError.notNull("formatType");
134 }
135 var regex = new RegExp('([^0]*)(0+)(.*)');
136 _patterns.forEach((int impliedDigits, String pattern) {
137 var match = regex.firstMatch(pattern);
138 var integerDigits = match.group(2).length;
139 var prefix = match.group(1);
140 var suffix = match.group(3);
141 // If the pattern is just zeros, with no suffix, then we shouldn't divide
142 // by the number of digits. e.g. for 'af', the pattern for 3 is '0', but
143 // it doesn't mean that 4321 should print as 4. But if the pattern was
144 // '0K', then it should print as '4K'. So we have to check if the pattern
145 // has a suffix. This seems extremely hacky, but I don't know how else to
146 // encode that. Check what other things are doing.
147 var divisor = 1;
148 if (pattern.replaceAll('0', '').isNotEmpty) {
149 divisor = pow(10, impliedDigits - integerDigits + 1);
150 }
151 var style = new _CompactStyle(
152 pattern: pattern,
153 requiredDigits: impliedDigits,
154 expectedDigits: integerDigits,
155 prefix: prefix,
156 suffix: suffix,
157 divisor: divisor);
158 _styles.add(style);
159 });
160 // Reverse the styles so that we look through them from largest to smallest.
161 _styles = _styles.reversed.toList();
162 // Add a fallback style that just prints the number.
163 _styles.add(new _CompactStyle());
164 }
165
166 /// The style in which we will format a particular number.
167 ///
168 /// This is a temporary variable that is only valid within a call to format.
169 _CompactStyle _style;
170
171 String format(number) {
172 _style = _styleFor(number);
173 var divisor = _style.printsAsIs ? 1 : _style.divisor;
174 var numberToFormat = _divide(number, divisor);
175 var formatted = super.format(numberToFormat);
176 var prefix = _style.prefix;
177 var suffix = _style.suffix;
178 // If this is for a currency, then the super call will have put the currency
179 // somewhere. We don't want it there, we want it where our style indicates,
180 // so remove it and replace. This has the remote possibility of a false
181 // positive, but it seems unlikely that e.g. USD would occur as a string in
182 // a regular number.
183 if (this._isForCurrency && !_style.isFallback) {
184 formatted = formatted.replaceFirst(currencySymbol, '').trim();
185 prefix = prefix.replaceFirst('\u00a4', currencySymbol);
186 suffix = suffix.replaceFirst('\u00a4', currencySymbol);
187 }
188 var withExtras = "${prefix}$formatted${suffix}";
189 _style = null;
190 return withExtras;
191 }
192
193 /// How many digits after the decimal place should we display, given that
194 /// there are [remainingSignificantDigits] left to show.
195 int _fractionDigitsAfter(int remainingSignificantDigits) {
196 var newFractionDigits =
197 super._fractionDigitsAfter(remainingSignificantDigits);
198 // For non-currencies, or for currencies if the numbers are large enough to
199 // compact, always use the number of significant digits and ignore
200 // decimalDigits. That is, $1.23K but also ¥12.3\u4E07, even though yen
201 // don't normally print decimal places.
202 if (!_isForCurrency || !_style.isFallback) return newFractionDigits;
203 // If we are printing a currency and it's too small to compact, but
204 // significant digits would have us only print some of the decimal digits,
205 // use all of them. So $12.30, not $12.3
206 if (newFractionDigits > 0 && newFractionDigits < decimalDigits) {
207 return decimalDigits;
208 } else {
209 return min(newFractionDigits, decimalDigits);
210 }
211 }
212
213 /// Divide numbers that may not have a division operator (e.g. Int64).
214 ///
215 /// Only used for powers of 10, so we require an integer denominator.
216 num _divide(numerator, int denominator) {
217 if (numerator is num) {
218 return numerator / denominator;
219 }
220 // If it doesn't fit in a JS int after division, we're not going to be able
221 // to meaningfully print a compact representation for it.
222 var divided = numerator ~/ denominator;
223 var integerPart = divided.toInt();
224 if (divided != integerPart) {
225 throw new FormatException(
226 "Number too big to use with compact format", numerator);
227 }
228 var remainder = numerator.remainder(denominator).toInt();
229 var originalFraction = numerator - (numerator ~/ 1);
230 var fraction = originalFraction == 0 ? 0 : originalFraction / denominator;
231 return integerPart + (remainder / denominator) + fraction;
232 }
233
234 _CompactStyle _styleFor(number) {
235 // We have to round the number based on the number of significant digits so
236 // that we pick the right style based on the rounded form and format 999999
237 // as 1M rather than 1000K.
238 var originalLength = NumberFormat.numberOfIntegerDigits(number);
239 var additionalDigits = originalLength - significantDigits;
240 var digitLength = originalLength;
241 if (additionalDigits > 0) {
242 var divisor = pow(10, additionalDigits);
243 // If we have an Int64, value speed over precision and make it double.
244 var rounded = (number.toDouble() / divisor).round() * divisor;
245 digitLength = NumberFormat.numberOfIntegerDigits(rounded);
246 }
247 for (var style in _styles) {
248 if (digitLength > style.totalDigits) {
249 return style;
250 }
251 }
252 throw new FormatException(
253 "No compact style found for number. This should not happen", number);
254 }
255
256 num parse(String text) {
257 for (var style in _styles.reversed) {
258 if (text.startsWith(style.prefix) && text.endsWith(style.suffix)) {
259 var numberText = text.substring(
260 style.prefix.length, text.length - style.suffix.length);
261 var number = _tryParsing(numberText);
262 if (number != null) {
263 return number * style.divisor;
264 }
265 }
266 }
267 throw new FormatException(
268 "Cannot parse compact number in locale '$locale'", text);
269 }
270
271 num _tryParsing(String text) {
272 try {
273 return super.parse(text);
274 } on FormatException {
275 return null;
276 }
277 }
278
279 CompactNumberSymbols get compactSymbols => compactNumberSymbols[_locale];
280 }
OLDNEW
« no previous file with comments | « packages/intl/lib/src/intl/bidi_utils.dart ('k') | packages/intl/lib/src/intl/date_format.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698