OLD | NEW |
---|---|
(Empty) | |
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 | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 #library("number_format"); | |
6 | |
7 #import("intl.dart"); | |
8 #import("number_symbols.dart"); | |
9 #import("number_symbols_data.dart"); | |
10 | |
11 class NumberFormat { | |
12 /** Variables to determine how number printing behaves. */ | |
13 String _negativePrefix = '-'; | |
14 String _positivePrefix = ''; | |
15 String _negativeSuffix = ''; | |
16 String _positiveSuffix = ''; | |
17 /** How many numbers in a group when using punctuation to group digits in | |
18 * large numbers. e.g. in en_US: "1,000,000" has a grouping size of 3 digits | |
19 * between commas. | |
20 */ | |
21 int _groupingSize = 3; | |
22 bool _decimalSeparatorAlwaysShown = false; | |
23 bool _useExponentialNotation = false; | |
24 int _maximumIntegerDigits = 40; | |
25 int _minimumIntegerDigits = 1; | |
26 int _maximumFractionDigits = 3; // invariant, >= minFractionDigits | |
27 int _minimumFractionDigits = 0; | |
28 int _minimumExponentDigits = 0; | |
29 bool _useSignForPositiveExponent = false; | |
30 | |
31 /** The locale in which we print numbers. */ | |
32 String _locale; | |
33 | |
34 /** Caches the symbols used for our locale. */ | |
35 NumberSymbols _symbols; | |
36 | |
37 /** | |
38 * Transient internal state in which to build up the result of the format | |
39 * operation. We can have this be just an instance variable because Dart is | |
40 * single-threaded and unless we do an asynchronous operation in the process | |
41 * of formatting then there will only ever be one number being formatted | |
42 * at a time. In languages with threads we'd need to pass this on the stack. | |
43 */ | |
44 StringBuffer _buffer; | |
45 | |
46 /** | |
47 * Create a number format that prints in [newPattern] as it applies in | |
48 * [locale]. | |
49 */ | |
50 NumberFormat([String newPattern, String locale]) { | |
51 // TODO(alanknight): There will need to be some kind of async setup | |
52 // operations so as not to bring along every locale in every program. | |
53 _locale = Intl.verifiedLocale(locale); | |
54 _setPattern(newPattern); | |
55 } | |
56 | |
57 /** | |
58 * Return the locale code in which we operate, e.g. 'en_US' or 'pt'. | |
59 */ | |
60 String get locale() => _locale; | |
61 | |
62 /** | |
63 * Return the symbols which are used in our locale. Cache them to avoid | |
64 * repeated lookup. | |
65 */ | |
66 NumberSymbols get symbols() { | |
67 if (_symbols == null) _symbols = numberFormatSymbols[locale]; | |
68 return _symbols; | |
69 } | |
70 | |
71 // TODO(alanknight): Actually use the pattern and locale. | |
72 _setPattern(String x) {} | |
73 | |
74 /** | |
75 * Format [number] according to our pattern and return the formatted string. | |
76 */ | |
77 String format(num number) { | |
78 // TODO(alanknight): Do we have to do anything for printing numbers bidi? | |
79 // Or are they always printed left to right? | |
80 if (number.isNaN()) return symbols.NAN; | |
81 if (number.isInfinite()) return "${_signPrefix(number)}${symbols.INFINITY}"; | |
82 | |
83 _newBuffer(); | |
84 _add(_signPrefix(number)); | |
85 _formatNumber(number.abs()); | |
86 _add(_signSuffix(number)); | |
87 | |
88 var result = _buffer.toString(); | |
89 _buffer = null; | |
90 return result; | |
91 } | |
92 | |
93 /** | |
94 * Format the main part of the number in the form dictated by the pattern. | |
95 */ | |
96 void _formatNumber(num number) { | |
97 if (_useExponentialNotation) { | |
98 _formatExponential(number); | |
99 } else { | |
100 _formatFixed(number); | |
101 } | |
102 } | |
103 | |
104 /** Format the number in exponential notation. */ | |
105 _formatExponential(num number) { | |
106 if (number == 0.0) { | |
107 _formatFixed(number); | |
108 _formatExponent(0); | |
109 return; | |
110 } | |
111 | |
112 var exponent = (Math.log(number) / Math.log(10)).floor(); | |
113 var mantissa = number / Math.pow(10, exponent); | |
114 | |
115 if (_minimumIntegerDigits < 1) { | |
116 exponent++; | |
117 mantissa /= 10; | |
118 } else { | |
119 exponent -= _minimumIntegerDigits - 1; | |
120 mantissa *= Math.pow(10, _minimumIntegerDigits - 1); | |
121 } | |
122 _formatFixed(number); | |
123 _formatExponent(exponent); | |
124 } | |
125 | |
126 /** | |
127 * Format the exponent portion, e.g. in "1.3e-5" the "e-5". | |
128 */ | |
129 void _formatExponent(num exponent) { | |
130 _add(symbols.EXP_SYMBOL); | |
131 if (exponent < 0) { | |
132 exponent = -exponent; | |
133 _add(symbols.MINUS_SIGN); | |
134 } else if (_useSignForPositiveExponent) { | |
135 _add(symbols.PLUS_SIGN); | |
136 } | |
137 _pad(_minimumExponentDigits, exponent.toString()); | |
138 } | |
139 | |
140 /** | |
141 * Format the basic number portion, inluding the fractional digits. | |
142 */ | |
143 void _formatFixed(num number) { | |
144 // Round the number. | |
145 var power = Math.pow(10, _maximumFractionDigits); | |
146 var intValue = number.truncate().toInt(); | |
147 var multiplied = (number * power).round(); | |
148 var fracValue = (multiplied - intValue * power).floor().toInt(); | |
149 var fractionPresent = _minimumFractionDigits > 0 || fracValue > 0; | |
150 | |
151 // On dartj2s the integer part may be large enough to be a floating | |
152 // point value, in which case we reduce it until it is small enough | |
153 // to be printed as an integer and pad the remainder with zeros. | |
154 var paddingDigits = new StringBuffer(); | |
155 while (intValue is! int) { | |
156 paddingDigits.add(symbols.ZERO_DIGIT); | |
157 intValue = (intValue / 10).toInt(); | |
158 } | |
159 var integerDigits = "${intValue}${paddingDigits}".charCodes(); | |
160 var digitLength = integerDigits.length; | |
161 | |
162 if (_hasPrintableIntegerPart(intValue)) { | |
163 _pad(_minimumIntegerDigits - digitLength); | |
164 for (var i = 0; i < digitLength; i++) { | |
165 _addDigit(integerDigits[i]); | |
166 _group(digitLength, i); | |
167 } | |
168 } else if (!fractionPresent) { | |
169 // If neither fraction nor integer part exists, just print zero. | |
170 _addZero(); | |
171 } | |
172 | |
173 _decimalSeparator(fractionPresent); | |
174 _formatFractionPart((fracValue + power).toString()); | |
175 } | |
176 | |
177 /** | |
178 * Format the part after the decimal place in a fixed point number. | |
179 */ | |
180 void _formatFractionPart(String fractionPart) { | |
181 var fractionCodes = fractionPart.charCodes(); | |
182 var fractionLength = fractionPart.length; | |
183 while (fractionPart[fractionLength - 1] == '0' && | |
184 fractionLength > _minimumFractionDigits + 1) { | |
185 fractionLength--; | |
186 } | |
187 for (var i = 1; i < fractionLength; i++) { | |
188 _addDigit(fractionCodes[i]); | |
189 } | |
190 } | |
191 | |
192 /** Print the decimal separator if appropriate. */ | |
193 void _decimalSeparator(bool fractionPresent) { | |
194 if (_decimalSeparatorAlwaysShown || fractionPresent) { | |
195 _add(symbols.DECIMAL_SEP); | |
196 } | |
197 } | |
198 | |
199 /** | |
200 * Return true if we have a main integer part which is printable, either | |
201 * because we have digits left of the decimal point, or because there are | |
202 * a minimum number of printable digits greater than 1. | |
203 */ | |
204 bool _hasPrintableIntegerPart(int intValue) { | |
205 return intValue > 0 || _minimumIntegerDigits > 0; | |
206 } | |
207 | |
208 /** | |
209 * Create a new empty buffer. See comment on [_buffer] variable for why | |
210 * we have it as an instance variable rather than passing it on the stack. | |
211 */ | |
212 void _newBuffer() { _buffer = new StringBuffer(); } | |
213 | |
214 /** A group of methods that provide support for writing digits and other | |
215 * required characters into [_buffer] easily. | |
216 */ | |
217 void _add(String x) { _buffer.add(x);} | |
218 void _addCharCode(int x) { _buffer.addCharCode(x); } | |
219 void _addZero() { _buffer.add(symbols.ZERO_DIGIT); } | |
220 void _addDigit(int x) { _buffer.addCharCode(_localeZero + x - _zero); } | |
221 | |
222 /** Print padding up to [numberOfDigits] above what's included in [basic]. */ | |
223 void _pad(int numberOfDigits, [String basic = '']) { | |
224 for (var i = 0; i < numberOfDigits - basic.length; i++) { | |
225 _add(symbols.ZERO_DIGIT); | |
226 } | |
227 for (var x in basic.charCodes()) { | |
228 _addDigit(x); | |
229 } | |
230 } | |
231 | |
232 /** | |
233 * We are printing the digits of the number from left to right. We may need | |
234 * to print a thousands separator or other grouping character as appropriate | |
235 * to the locale. So we find how many places we are from the end of the number | |
236 * by subtracting our current [position] from the [totalLength] and print | |
237 * the separator character every [_groupingSize] digits. | |
238 */ | |
239 void _group(int totalLength, int position) { | |
240 var distanceFromEnd = totalLength - position; | |
241 if (distanceFromEnd <=1 || _groupingSize <= 0) return; | |
Emily Fortuna
2012/08/16 00:10:24
spacing:
distanceFromEnd <= 1
Alan Knight
2012/08/16 16:41:50
Done.
| |
242 if (distanceFromEnd % _groupingSize == 1) { | |
243 _add(symbols.GROUP_SEP); | |
244 } | |
245 } | |
246 | |
247 /** Returns the code point for the character '0'. */ | |
248 int get _zero() => '0'.charCodes()[0]; | |
249 | |
250 /** Returns the code point for the locale's zero digit. */ | |
251 int get _localeZero() => symbols.ZERO_DIGIT.charCodeAt(0); | |
252 | |
253 /** | |
254 * Returns the prefix for [x] based on whether it's positive or negative. | |
255 * In en_US this would be '' and '-' respectively. | |
256 */ | |
257 String _signPrefix(num x) { | |
258 return x.isNegative() ? _negativePrefix : _positivePrefix; | |
259 } | |
260 | |
261 /** | |
262 * Returns the suffix for [x] based on wether it's positive or negative. | |
263 * In en_US there are no suffixes for positive or negative. | |
264 */ | |
265 String _signSuffix(num x) { | |
266 return x.isNegative() ? _negativeSuffix : _positiveSuffix; | |
267 } | |
268 } | |
OLD | NEW |