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