Index: pkg/i18n/number_format.dart |
=================================================================== |
--- pkg/i18n/number_format.dart (revision 0) |
+++ pkg/i18n/number_format.dart (revision 0) |
@@ -0,0 +1,258 @@ |
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
+// for details. All rights reserved. Use of this source code is governed by a |
+// BSD-style license that can be found in the LICENSE file. |
+ |
+#library("number_format"); |
Emily Fortuna
2012/08/15 21:38:16
nit: space between library and imports section?
Alan Knight
2012/08/15 23:57:44
Done.
|
+#import("intl.dart"); |
+#import("number_symbols.dart"); |
+#import("number_symbols_data.dart"); |
+ |
+class NumberFormat { |
+ |
Emily Fortuna
2012/08/15 21:38:16
remove extra newline
Alan Knight
2012/08/15 23:57:44
Done.
|
+ /** |
+ * Create a number format that prints in [newPattern] as it applies in |
+ * [locale]. |
+ */ |
+ NumberFormat([String newPattern, String locale]) { |
Emily Fortuna
2012/08/15 21:38:16
Can you add a TODO around here reminding us this A
Alan Knight
2012/08/15 23:57:44
Done.
|
+ _locale = Intl.verifiedLocale(locale); |
+ _setPattern(newPattern); |
+ } |
+ |
+ /** Variables to determine how number printing behaves. */ |
+ String _negativePrefix = '-'; |
Emily Fortuna
2012/08/15 21:38:16
put fields above the constructor.
Alan Knight
2012/08/15 23:57:44
Done.
|
+ String _positivePrefix = ''; |
+ String _negativeSuffix = ''; |
+ String _positiveSuffix = ''; |
+ num _multiplier = 1; |
+ num _groupingSize = 3; |
Emily Fortuna
2012/08/15 21:38:16
what's this one mean? add a comment?
Alan Knight
2012/08/15 23:57:44
Done.
|
+ bool _decimalSeparatorAlwaysShown = false; |
+ bool _useExponentialNotation = false; |
+ int _maximumIntegerDigits = 40; |
Emily Fortuna
2012/08/15 21:38:16
how are these determined?
Alan Knight
2012/08/15 23:57:44
These are hard-coded versions appropriate for an e
|
+ int _minimumIntegerDigits = 1; |
Emily Fortuna
2012/08/15 21:38:16
should these be final?
Alan Knight
2012/08/15 23:57:44
They will depend on the locale. At least with the
Emily Fortuna
2012/08/16 00:10:24
What about declaring the variable as final and ini
Alan Knight
2012/08/16 16:41:49
Hmm. I didn't think it was, but now it looks like
|
+ int _maximumFractionDigits = 3; // invariant, >= minFractionDigits |
+ int _minimumFractionDigits = 0; |
+ int _minimumExponentDigits = 0; |
+ bool _useSignForPositiveExponent = false; |
+ |
+ /** The locale in which we print numbers. */ |
+ String _locale; |
+ |
+ /** Caches the symbols used for our locale. */ |
+ NumberSymbols _symbols; |
+ |
+ /** |
+ * Transient internal state in which to build up the result of the format |
+ * operation. We can have this be just an instance variable because Dart is |
Emily Fortuna
2012/08/15 21:38:16
indentation
Alan Knight
2012/08/15 23:57:44
Done.
|
+ * single-threaded and unless we do an asynchronous operation in the process |
+ * of formatting then there will only ever be one number being formatted |
+ * at a time. In languages with threads we'd need to pass this on the stack. |
Emily Fortuna
2012/08/15 21:38:16
Is this comment relevant? We don't have threads...
Alan Knight
2012/08/15 23:57:44
I'm saying we can do this precisely because we don
Emily Fortuna
2012/08/16 00:10:24
Gotcha. I wasn't reading this carefully.
|
+ */ |
+ StringBuffer _buffer; |
+ |
+ /** |
+ * Return the locale code in which we operate, e.g. 'en_US' or 'pt'. |
+ */ |
+ String get locale() => _locale; |
+ |
+ /** |
+ * Return the symbols which are used in our locale. Cache them to avoid |
+ * repeated lookup. |
+ */ |
+ NumberSymbols get symbols() { |
+ if (_symbols == null) _symbols = numberFormatSymbols[locale]; |
+ return _symbols; |
+ } |
+ |
+ // TODO(alanknight): Actually use the pattern and locale. |
+ _setPattern(x) {} |
Emily Fortuna
2012/08/15 21:38:16
type for arg x?
Alan Knight
2012/08/15 23:57:44
Done.
|
+ |
+ /** |
+ * Format [number] according to our pattern and return the formatted string. |
+ */ |
+ String format(num number) { |
+ if (number.isNaN()) return symbols.NAN; |
+ if (number.isInfinite()) return "${_signPrefix(number)}${symbols.INFINITY}"; |
+ |
+ _newBuffer(); |
+ _add(_signPrefix(number)); |
+ _formatNumber(number.abs()); |
+ _add(_signSuffix(number)); |
+ |
+ var result = _buffer.toString(); |
+ _buffer = null; |
+ return result; |
+ } |
+ |
+ /** |
+ * Format the main part of the number in the form dictated by the pattern. |
+ */ |
+ void _formatNumber(num number) { |
+ if (_useExponentialNotation) { |
+ _formatExponential(number); |
Emily Fortuna
2012/08/15 21:38:16
indentation :-(
Alan Knight
2012/08/15 23:57:44
Done.
|
+ } else { |
+ _formatFixed(number); |
+ } |
+ } |
+ |
+ /** Format the number in exponential notation. */ |
+ _formatExponential(num number) { |
+ if (number == 0.0) { |
+ _formatFixed(number); |
+ _formatExponent(0); |
+ return; |
+ } |
+ |
+ var exponent = (Math.log(number) / Math.log(10)).floor(); |
+ var mantissa = number / Math.pow(10, exponent); |
+ |
+ if (_minimumIntegerDigits < 1) { |
+ exponent++; |
+ mantissa /= 10; |
+ } else { |
+ exponent -= _minimumIntegerDigits - 1; |
+ mantissa *= Math.pow(10, _minimumIntegerDigits - 1); |
+ } |
+ _formatFixed(number); |
+ _formatExponent(exponent); |
+ } |
+ |
+ /** |
+ * Format the exponent portion, e.g. in "1.3e-5" the "e-5". |
+ */ |
+ void _formatExponent(num exponent) { |
+ _add(symbols.EXP_SYMBOL); |
+ if (exponent < 0) { |
+ exponent = -exponent; |
+ _add(symbols.MINUS_SIGN); |
+ } else if (_useSignForPositiveExponent) { |
+ _add(symbols.PLUS_SIGN); |
+ } |
+ _pad(_minimumExponentDigits, exponent.toString()); |
+ } |
+ |
+ /** |
+ * Format the basic number portion, inluding the digits after the decimal |
Emily Fortuna
2012/08/15 21:38:16
"digits after the decimal point" --> "fractional d
Alan Knight
2012/08/15 23:57:44
Done.
|
+ * point. |
+ */ |
+ void _formatFixed(num number) { |
+ // round the number |
Emily Fortuna
2012/08/15 21:38:16
Comments start with a capital letter and end in a
Alan Knight
2012/08/15 23:57:44
Done.
|
+ var power = Math.pow(10, _maximumFractionDigits); |
+ var intValue = number.truncate().toInt(); |
+ var multiplied = (number * power).round(); |
+ var fracValue = (multiplied - intValue * power).floor().toInt(); |
+ |
Emily Fortuna
2012/08/15 21:38:16
is this an extra newline here?
Alan Knight
2012/08/15 23:57:44
Done.
|
+ var fractionPresent = _minimumFractionDigits > 0 || fracValue > 0; |
+ |
+ // On dartj2s the integer part may be large enough to be a floating |
Emily Fortuna
2012/08/15 21:38:16
but we just populated intValue = number.truncate()
Alan Knight
2012/08/15 23:57:44
Sadly, that's not true on dart2js. Like, seriously
|
+ // point value, in which case we reduce it until it is small enough |
+ // to be printed as an integer and pad the remainder with zeros. |
+ var paddingDigits = new StringBuffer(); |
+ while (intValue is! int) { |
+ paddingDigits.add(symbols.ZERO_DIGIT); |
+ intValue = (intValue / 10).toInt(); |
+ } |
+ var integerDigits = "${intValue}${paddingDigits}".charCodes(); |
+ var digitLength = integerDigits.length; |
+ |
+ if (_hasPrintableIntegerPart(intValue)) { |
+ _pad(_minimumIntegerDigits - digitLength); |
+ for (var i = 0; i < digitLength; i++) { |
+ _addDigit(integerDigits[i]); |
Emily Fortuna
2012/08/15 21:38:16
question -- are numbers always RTL, or are there s
Alan Knight
2012/08/15 23:57:44
Good question. Added a TODO. At the moment this mi
|
+ _group(digitLength, i); |
+ } |
+ } else if (!fractionPresent) { |
+ // If neither fraction nor integer part exists, just print zero. |
+ _addZero(); |
+ } |
+ |
+ _decimalSeparator(fractionPresent); |
+ _formatFractionPart((fracValue + power).toString()); |
+ } |
+ |
+ /** |
+ * Format the part after the decimal place in a fixed point number. |
+ */ |
+ void _formatFractionPart(String fractionPart) { |
+ var fractionCodes = fractionPart.charCodes(); |
+ var fractionLength = fractionPart.length; |
+ while (fractionPart[fractionLength - 1] == '0' && |
+ fractionLength > _minimumFractionDigits + 1) { |
+ fractionLength--; |
+ } |
+ for (var i = 1; i < fractionLength; i++) { |
+ _addDigit(fractionCodes[i]); |
+ } |
+ } |
+ |
+ /** Print the decimal separator if appropriate. */ |
+ void _decimalSeparator(bool fractionPresent) { |
+ if (_decimalSeparatorAlwaysShown || fractionPresent) { |
+ _add(symbols.DECIMAL_SEP); |
+ } |
+ } |
+ |
+ /** |
+ * Return true if we have a main integer part which is printable, either |
+ * because we have digits left of the decimal point, or because there are |
+ * a minimum number of printable digits greater than 1. |
+ */ |
+ bool _hasPrintableIntegerPart(int intValue) => intValue > 0 || |
Emily Fortuna
2012/08/15 21:38:16
if the function is more than one line, make it use
Alan Knight
2012/08/15 23:57:44
Done.
|
+ _minimumIntegerDigits > 0; |
+ |
+ /** |
+ * Create a new empty buffer. See comment on [_buffer] variable for why |
+ * we have it as an instance variable rather than passing it on the stack. |
+ */ |
+ void _newBuffer() { _buffer = new StringBuffer();} |
Emily Fortuna
2012/08/15 21:38:16
space after ;
Alan Knight
2012/08/15 23:57:44
Done.
|
+ |
+ /** A group of methods that provide support for writing digits and other |
+ * required characters into [_buffer] easily. |
+ */ |
+ void _add(String x) { _buffer.add(x);} |
Emily Fortuna
2012/08/15 21:38:16
space after ; , here and below
Alan Knight
2012/08/15 23:57:44
Done.
|
+ void _addCharCode(int x) { _buffer.addCharCode(x);} |
+ void _addZero() { _buffer.add(symbols.ZERO_DIGIT);} |
+ void _addDigit(int x) { _buffer.addCharCode(_localeZero + x - _zero);} |
+ |
+ /** Print padding up to [numberOfDigits] above what's included in [basic]. */ |
+ void _pad(int numberOfDigits, [String basic = '']) { |
+ for (var i = 0; i < numberOfDigits - basic.length; i++) { |
+ _add(symbols.ZERO_DIGIT); |
+ } |
+ for (var x in basic.charCodes()) { |
+ _addDigit(x); |
+ } |
+ } |
+ |
+ /** |
+ * Print the grouping symbol (e.g. comma as thousands separator) for digits |
+ * left of the decimal place if it's required at this position. |
+ */ |
+ String _group(int digitLen, int position) { |
Emily Fortuna
2012/08/15 21:38:16
what do digitLen vs position actually mean?
Alan Knight
2012/08/15 23:57:44
Yes, that method was confusing. Rewrote and improv
|
+ if (digitLen - position > 1 && _groupingSize > 0 && |
+ ((digitLen - position) % _groupingSize == 1)) { |
+ _add(symbols.GROUP_SEP); |
+ } |
+ } |
+ |
+ /** Returns the code point for the character '0'. */ |
+ int get _zero() => '0'.charCodes()[0]; |
+ |
+ /** Returns the code point for the locale's zero digit. */ |
+ int get _localeZero() => symbols.ZERO_DIGIT.charCodeAt(0); |
+ |
+ /** |
+ * Returns the prefix for [x] based on wether it's positive or negative. |
Emily Fortuna
2012/08/15 21:38:16
wether -> whether
Alan Knight
2012/08/15 23:57:44
Done.
|
+ * In en_US this would be '' and '-' respectively. |
+ */ |
+ String _signPrefix(num x) { |
+ return x.isNegative() ? _negativePrefix : _positivePrefix; |
+ } |
+ |
+ /** |
+ * Returns the suffix for [x] based on wether it's positive or negative. |
+ * In en_US there are no suffixes for positive or negative. |
+ */ |
+ String _signSuffix(num x) { |
+ return x.isNegative() ? _negativeSuffix : _positiveSuffix; |
+ } |
+} |