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

Side by Side Diff: pkg/intl/lib/src/intl/number_format.dart

Issue 778293002: Make number formatting in Intl able to work with Int64 or other types. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Increment version, update CHANGELOG Created 6 years 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 | Annotate | Revision Log
« no previous file with comments | « pkg/intl/CHANGELOG.md ('k') | pkg/intl/pubspec.yaml » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 * Provides the ability to format a number in a locale-specific way. The 7 * Provides the ability to format a number in a locale-specific way. The
8 * format is specified as a pattern using a subset of the ICU formatting 8 * format is specified as a pattern using a subset of the ICU formatting
9 * patterns. 9 * patterns.
10 * 10 *
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
64 bool _decimalSeparatorAlwaysShown = false; 64 bool _decimalSeparatorAlwaysShown = false;
65 bool _useSignForPositiveExponent = false; 65 bool _useSignForPositiveExponent = false;
66 bool _useExponentialNotation = false; 66 bool _useExponentialNotation = false;
67 67
68 int maximumIntegerDigits = 40; 68 int maximumIntegerDigits = 40;
69 int minimumIntegerDigits = 1; 69 int minimumIntegerDigits = 1;
70 int maximumFractionDigits = 3; 70 int maximumFractionDigits = 3;
71 int minimumFractionDigits = 0; 71 int minimumFractionDigits = 0;
72 int minimumExponentDigits = 0; 72 int minimumExponentDigits = 0;
73 73
74 int _multiplier = 1; 74 /**
75 * For percent and permille, what are we multiplying by in order to
76 * get the printed value, e.g. 100 for percent.
77 */
78 int get _multiplier => _internalMultiplier;
79 set _multiplier(int x) {
80 _internalMultiplier = x;
81 _multiplierDigits = (log(_multiplier) / LN10).round();
82 }
83 int _internalMultiplier = 1;
84
85 /** How many digits are there in the [_multiplier]. */
86 int _multiplierDigits = 0;
75 87
76 /** 88 /**
77 * Stores the pattern used to create this format. This isn't used, but 89 * Stores the pattern used to create this format. This isn't used, but
78 * is helpful in debugging. 90 * is helpful in debugging.
79 */ 91 */
80 String _pattern; 92 String _pattern;
81 93
82 /** The locale in which we print numbers. */ 94 /** The locale in which we print numbers. */
83 final String _locale; 95 final String _locale;
84 96
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after
155 167
156 /** 168 /**
157 * Return the symbols which are used in our locale. Cache them to avoid 169 * Return the symbols which are used in our locale. Cache them to avoid
158 * repeated lookup. 170 * repeated lookup.
159 */ 171 */
160 NumberSymbols get symbols => _symbols; 172 NumberSymbols get symbols => _symbols;
161 173
162 /** 174 /**
163 * Format [number] according to our pattern and return the formatted string. 175 * Format [number] according to our pattern and return the formatted string.
164 */ 176 */
165 String format(num number) { 177 String format(number) {
166 // TODO(alanknight): Do we have to do anything for printing numbers bidi? 178 if (_isNaN(number)) return symbols.NAN;
167 // Or are they always printed left to right? 179 if (_isInfinite(number)) return "${_signPrefix(number)}${symbols.INFINITY}";
168 if (number.isNaN) return symbols.NAN;
169 if (number.isInfinite) return "${_signPrefix(number)}${symbols.INFINITY}";
170 180
171 _add(_signPrefix(number)); 181 _add(_signPrefix(number));
172 _formatNumber(number.abs() * _multiplier); 182 _formatNumber(number.abs());
173 _add(_signSuffix(number)); 183 _add(_signSuffix(number));
174 184
175 var result = _buffer.toString(); 185 var result = _buffer.toString();
176 _buffer.clear(); 186 _buffer.clear();
177 return result; 187 return result;
178 } 188 }
179 189
180 /** 190 /**
181 * Parse the number represented by the string. If it's not 191 * Parse the number represented by the string. If it's not
182 * parseable, throws a [FormatException]. 192 * parseable, throws a [FormatException].
183 */ 193 */
184 num parse(String text) => new _NumberParser(this, text).value; 194 num parse(String text) => new _NumberParser(this, text).value;
185 195
186 /** 196 /**
187 * Format the main part of the number in the form dictated by the pattern. 197 * Format the main part of the number in the form dictated by the pattern.
188 */ 198 */
189 void _formatNumber(num number) { 199 void _formatNumber(number) {
190 if (_useExponentialNotation) { 200 if (_useExponentialNotation) {
191 _formatExponential(number); 201 _formatExponential(number);
192 } else { 202 } else {
193 _formatFixed(number); 203 _formatFixed(number);
194 } 204 }
195 } 205 }
196 206
197 /** Format the number in exponential notation. */ 207 /** Format the number in exponential notation. */
198 void _formatExponential(num number) { 208 void _formatExponential(num number) {
199 if (number == 0.0) { 209 if (number == 0.0) {
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
243 } else if (_useSignForPositiveExponent) { 253 } else if (_useSignForPositiveExponent) {
244 _add(symbols.PLUS_SIGN); 254 _add(symbols.PLUS_SIGN);
245 } 255 }
246 _pad(minimumExponentDigits, exponent.toString()); 256 _pad(minimumExponentDigits, exponent.toString());
247 } 257 }
248 258
249 /** Used to test if we have exceeded Javascript integer limits. */ 259 /** Used to test if we have exceeded Javascript integer limits. */
250 final _maxInt = pow(2, 52); 260 final _maxInt = pow(2, 52);
251 261
252 /** 262 /**
263 * Helpers to check numbers that don't conform to the [num] interface,
264 * e.g. Int64
265 */
266 _isInfinite(number) => number is num ? number.isInfinite : false;
267 _isNaN(number) => number is num ? number.isNaN : false;
268 _round(number) => number is num ? number.round() : number;
269 _floor(number) => number is num ? number.floor() : number;
270
271 /**
253 * Format the basic number portion, inluding the fractional digits. 272 * Format the basic number portion, inluding the fractional digits.
254 */ 273 */
255 void _formatFixed(num number) { 274 void _formatFixed(number) {
256 // Very fussy math to get integer and fractional parts. 275 var integerPart;
257 var power = pow(10, maximumFractionDigits); 276 int fractionPart;
258 var shiftedNumber = (number * power); 277 int extraIntegerDigits;
259 // We must not roundToDouble() an int or it will lose precision. We must not 278
260 // round() a large double or it will take its loss of precision and 279 final power = pow(10, maximumFractionDigits);
261 // preserve it in an int, which we will then print to the right 280 final digitMultiplier = power * _multiplier;
262 // of the decimal place. Therefore, only roundToDouble if we are already 281
263 // a double. 282 if (_isInfinite(number)) {
264 if (shiftedNumber is double) { 283 integerPart = number.toInt();
265 shiftedNumber = shiftedNumber.roundToDouble(); 284 extraIntegerDigits = 0;
285 fractionPart = 0;
286 } else {
287 // We have three possible pieces. First, the basic integer part. If this
288 // is a percent or permille, the additional 2 or 3 digits. Finally the
289 // fractional part.
290 // We avoid multiplying the number because it might overflow if we have
291 // a fixed-size integer type, so we extract each of the three as an
292 // integer pieces.
293 integerPart = _floor(number);
294 var fraction = number - integerPart;
295 // Multiply out to the number of decimal places and the percent, then
296 // round. For fixed-size integer types this should always be zero, so
297 // multiplying is OK.
298 var remainingDigits = _round(fraction * digitMultiplier).toInt();
299 // However, in rounding we may overflow into the main digits.
300 if (remainingDigits >= digitMultiplier) {
301 integerPart++;
302 remainingDigits -= digitMultiplier;
303 }
304 // Separate out the extra integer parts from the fraction part.
305 extraIntegerDigits = remainingDigits ~/ power;
306 fractionPart = remainingDigits % power;
266 } 307 }
267 var intValue, fracValue; 308 var fractionPresent = minimumFractionDigits > 0 || fractionPart > 0;
268 if (shiftedNumber.isInfinite) {
269 intValue = number.toInt();
270 fracValue = 0;
271 } else {
272 intValue = shiftedNumber.round() ~/ power;
273 fracValue = (shiftedNumber - intValue * power).floor();
274 }
275 var fractionPresent = minimumFractionDigits > 0 || fracValue > 0;
276 309
277 // If the int part is larger than 2^52 and we're on Javascript (so it's 310 var integerDigits = _integerDigits(integerPart, extraIntegerDigits);
278 // really a float) it will lose precision, so pad out the rest of it
279 // with zeros. Check for Javascript by seeing if an integer is double.
280 var paddingDigits = '';
281 if (1 is double && intValue > _maxInt) {
282 var howManyDigitsTooBig = (log(intValue) / LN10).ceil() - 16;
283 var divisor = pow(10, howManyDigitsTooBig).round();
284 paddingDigits = symbols.ZERO_DIGIT * howManyDigitsTooBig.toInt();
285
286 intValue = (intValue / divisor).truncate();
287 }
288 var integerDigits = "${intValue}${paddingDigits}".codeUnits;
289 var digitLength = integerDigits.length; 311 var digitLength = integerDigits.length;
290 312
291 if (_hasPrintableIntegerPart(intValue)) { 313 if (_hasPrintableIntegerPart(integerPart)) {
292 _pad(minimumIntegerDigits - digitLength); 314 _pad(minimumIntegerDigits - digitLength);
293 for (var i = 0; i < digitLength; i++) { 315 for (var i = 0; i < digitLength; i++) {
294 _addDigit(integerDigits[i]); 316 _addDigit(integerDigits.codeUnitAt(i));
295 _group(digitLength, i); 317 _group(digitLength, i);
296 } 318 }
297 } else if (!fractionPresent) { 319 } else if (!fractionPresent) {
298 // If neither fraction nor integer part exists, just print zero. 320 // If neither fraction nor integer part exists, just print zero.
299 _addZero(); 321 _addZero();
300 } 322 }
301 323
302 _decimalSeparator(fractionPresent); 324 _decimalSeparator(fractionPresent);
303 _formatFractionPart((fracValue + power).toString()); 325 _formatFractionPart((fractionPart + power).toString());
304 } 326 }
305 327
306 /** 328 /**
329 * Compute the raw integer digits which will then be printed with
330 * grouping and translated to localized digits.
331 */
332 String _integerDigits(integerPart, extraIntegerDigits) {
333 // If the int part is larger than 2^52 and we're on Javascript (so it's
334 // really a float) it will lose precision, so pad out the rest of it
335 // with zeros. Check for Javascript by seeing if an integer is double.
336 var paddingDigits = '';
337 if (1 is double && integerPart is num && integerPart > _maxInt) {
338 var howManyDigitsTooBig = (log(integerPart) / LN10).ceil() - 16;
339 var divisor = pow(10, howManyDigitsTooBig).round();
340 paddingDigits = symbols.ZERO_DIGIT * howManyDigitsTooBig.toInt();
341 integerPart = (integerPart / divisor).truncate();
342 }
343
344 var extra = extraIntegerDigits == 0 ? '' : extraIntegerDigits.toString();
345 var intDigits = _mainIntegerDigits(integerPart);
346 var paddedExtra =
347 intDigits.isEmpty ? extra : extra.padLeft(_multiplierDigits, '0');
348 return "${intDigits}${paddedExtra}${paddingDigits}";
349 }
350
351 /**
352 * The digit string of the integer part. This is the empty string if the
353 * integer part is zero and otherwise is the toString() of the integer
354 * part, stripping off any minus sign.
355 */
356 String _mainIntegerDigits(integer) {
357 if (integer == 0) return '';
358 var digits = integer.toString();
359 // If we have a fixed-length int representation, it can have a negative
360 // number whose negation is also negative, e.g. 2^-63 in 64-bit.
361 // Remove the minus sign.
362 return digits.startsWith('-') ? digits.substring(1) : digits;
363 }
364
365 /**
307 * Format the part after the decimal place in a fixed point number. 366 * Format the part after the decimal place in a fixed point number.
308 */ 367 */
309 void _formatFractionPart(String fractionPart) { 368 void _formatFractionPart(String fractionPart) {
310 var fractionCodes = fractionPart.codeUnits; 369 var fractionCodes = fractionPart.codeUnits;
311 var fractionLength = fractionPart.length; 370 var fractionLength = fractionPart.length;
312 while (fractionCodes[fractionLength - 1] == _zero && 371 while (fractionCodes[fractionLength - 1] == _zero &&
313 fractionLength > minimumFractionDigits + 1) { 372 fractionLength > minimumFractionDigits + 1) {
314 fractionLength--; 373 fractionLength--;
315 } 374 }
316 for (var i = 1; i < fractionLength; i++) { 375 for (var i = 1; i < fractionLength; i++) {
317 _addDigit(fractionCodes[i]); 376 _addDigit(fractionCodes[i]);
318 } 377 }
319 } 378 }
320 379
321 /** Print the decimal separator if appropriate. */ 380 /** Print the decimal separator if appropriate. */
322 void _decimalSeparator(bool fractionPresent) { 381 void _decimalSeparator(bool fractionPresent) {
323 if (_decimalSeparatorAlwaysShown || fractionPresent) { 382 if (_decimalSeparatorAlwaysShown || fractionPresent) {
324 _add(symbols.DECIMAL_SEP); 383 _add(symbols.DECIMAL_SEP);
325 } 384 }
326 } 385 }
327 386
328 /** 387 /**
329 * Return true if we have a main integer part which is printable, either 388 * Return true if we have a main integer part which is printable, either
330 * because we have digits left of the decimal point, or because there are 389 * because we have digits left of the decimal point, or because there are
331 * a minimum number of printable digits greater than 1. 390 * a minimum number of printable digits greater than 1.
332 */ 391 */
333 bool _hasPrintableIntegerPart(int intValue) => 392 bool _hasPrintableIntegerPart(x) =>
334 intValue > 0 || minimumIntegerDigits > 0; 393 x > 0 || minimumIntegerDigits > 0;
335 394
336 /** A group of methods that provide support for writing digits and other 395 /** A group of methods that provide support for writing digits and other
337 * required characters into [_buffer] easily. 396 * required characters into [_buffer] easily.
338 */ 397 */
339 void _add(String x) { _buffer.write(x);} 398 void _add(String x) { _buffer.write(x);}
340 void _addCharCode(int x) { _buffer.writeCharCode(x);} 399 void _addCharCode(int x) { _buffer.writeCharCode(x);}
341 void _addZero() { _buffer.write(symbols.ZERO_DIGIT);} 400 void _addZero() { _buffer.write(symbols.ZERO_DIGIT);}
342 void _addDigit(int x) { _buffer.writeCharCode(_localeZero + x - _zero);} 401 void _addDigit(int x) { _buffer.writeCharCode(_localeZero + x - _zero);}
343 402
344 /** Print padding up to [numberOfDigits] above what's included in [basic]. */ 403 /** Print padding up to [numberOfDigits] above what's included in [basic]. */
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
377 // Note that there is a slight risk of a locale's zero digit not fitting 436 // Note that there is a slight risk of a locale's zero digit not fitting
378 // into a single code unit, but it seems very unlikely, and if it did, 437 // into a single code unit, but it seems very unlikely, and if it did,
379 // there's a pretty good chance that our assumptions about being able to do 438 // there's a pretty good chance that our assumptions about being able to do
380 // arithmetic on it would also be invalid. 439 // arithmetic on it would also be invalid.
381 get _localeZero => symbols.ZERO_DIGIT.codeUnits.first; 440 get _localeZero => symbols.ZERO_DIGIT.codeUnits.first;
382 441
383 /** 442 /**
384 * Returns the prefix for [x] based on whether it's positive or negative. 443 * Returns the prefix for [x] based on whether it's positive or negative.
385 * In en_US this would be '' and '-' respectively. 444 * In en_US this would be '' and '-' respectively.
386 */ 445 */
387 String _signPrefix(num x) => x.isNegative ? _negativePrefix : _positivePrefix; 446 String _signPrefix(x) => x.isNegative ? _negativePrefix : _positivePrefix;
388 447
389 /** 448 /**
390 * Returns the suffix for [x] based on wether it's positive or negative. 449 * Returns the suffix for [x] based on wether it's positive or negative.
391 * In en_US there are no suffixes for positive or negative. 450 * In en_US there are no suffixes for positive or negative.
392 */ 451 */
393 String _signSuffix(num x) => x.isNegative ? _negativeSuffix : _positiveSuffix; 452 String _signSuffix(x) => x.isNegative ? _negativeSuffix : _positiveSuffix;
394 453
395 void _setPattern(String newPattern) { 454 void _setPattern(String newPattern) {
396 if (newPattern == null) return; 455 if (newPattern == null) return;
397 // Make spaces non-breaking 456 // Make spaces non-breaking
398 _pattern = newPattern.replaceAll(' ', '\u00a0'); 457 _pattern = newPattern.replaceAll(' ', '\u00a0');
399 var parser = new _NumberFormatParser(this, newPattern, currencyName); 458 var parser = new _NumberFormatParser(this, newPattern, currencyName);
400 parser.parse(); 459 parser.parse();
401 } 460 }
402 461
403 String toString() => "NumberFormat($_locale, $_pattern)"; 462 String toString() => "NumberFormat($_locale, $_pattern)";
(...skipping 597 matching lines...) Expand 10 before | Expand all | Expand 10 after
1001 String get peek => nextIndex >= input.length ? null : input[nextIndex]; 1060 String get peek => nextIndex >= input.length ? null : input[nextIndex];
1002 1061
1003 Iterator<String> get iterator => this; 1062 Iterator<String> get iterator => this;
1004 1063
1005 static String _validate(input) { 1064 static String _validate(input) {
1006 if (input is! String) throw new ArgumentError(input); 1065 if (input is! String) throw new ArgumentError(input);
1007 return input; 1066 return input;
1008 } 1067 }
1009 1068
1010 } 1069 }
OLDNEW
« no previous file with comments | « pkg/intl/CHANGELOG.md ('k') | pkg/intl/pubspec.yaml » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698