NumberFormat class
Provides the ability to format a number in a locale-specific way. The format is specified as a pattern using a subset of the ICU formatting patterns.
0
A single digit#
A single digit, omitted if the value is zero.
Decimal separator-
Minus sign,
Grouping separatorE
Separates mantissa and expontent+
- Before an exponent, indicates it should be prefixed with a plus sign.%
- In prefix or suffix, multiply by 100 and show as percentage‰ (\u2030)
In prefix or suffix, multiply by 1000 and show as per mille¤ (\u00A4)
Currency sign, replaced by currency name'
Used to quote special characters;
Used to separate the positive and negative patterns if both are present
For example,
var f = new NumberFormat("###.0#", "en_US");
print(f.format(12.345));
==> 12.34
If the locale is not specified, it will default to the current locale. If the format is not specified it will print in a basic format with at least one integer digit and three fraction digits.
There are also standard patterns available via the special constructors. e.g.
var symbols = new NumberFormat.percentFormat("ar");
There are four such constructors: decimalFormat, percentFormat, scientificFormat and currencyFormat. However, at the moment, scientificFormat prints only as equivalent to "#E0" and does not take into account significant digits. currencyFormat will always use the name of the currency rather than the symbol.
class NumberFormat { /** Variables to determine how number printing behaves. */ // TODO(alanknight): If these remain as variables and are set based on the // pattern, can we make them final? String _negativePrefix = '-'; String _positivePrefix = ''; String _negativeSuffix = ''; String _positiveSuffix = ''; /** * How many numbers in a group when using punctuation to group digits in * large numbers. e.g. in en_US: "1,000,000" has a grouping size of 3 digits * between commas. */ int _groupingSize = 3; bool _decimalSeparatorAlwaysShown = false; bool _useSignForPositiveExponent = false; bool _useExponentialNotation = false; int maximumIntegerDigits = 40; int minimumIntegerDigits = 1; int maximumFractionDigits = 3; int minimumFractionDigits = 0; int minimumExponentDigits = 0; int _multiplier = 1; /** * Stores the pattern used to create this format. This isn't used, but * is helpful in debugging. */ String _pattern; /** The locale in which we print numbers. */ final 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 * 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. */ StringBuffer _buffer; /** * Create a number format that prints using [newPattern] as it applies in * [locale]. */ factory NumberFormat([String newPattern, String locale]) { return new NumberFormat._forPattern(locale, (x) => newPattern); } /** Create a number format that prints as DECIMAL_PATTERN. */ NumberFormat.decimalPattern([String locale]) : this._forPattern(locale, (x) => x.DECIMAL_PATTERN); /** Create a number format that prints as PERCENT_PATTERN. */ NumberFormat.percentPattern([String locale]) : this._forPattern(locale, (x) => x.PERCENT_PATTERN); /** Create a number format that prints as SCIENTIFIC_PATTERN. */ NumberFormat.scientificPattern([String locale]) : this._forPattern(locale, (x) => x.SCIENTIFIC_PATTERN); /** Create a number format that prints as CURRENCY_PATTERN. */ NumberFormat.currencyPattern([String locale]) : this._forPattern(locale, (x) => x.CURRENCY_PATTERN); /** * Create a number format that prints in a pattern we get from * the [getPattern] function using the locale [locale]. */ NumberFormat._forPattern(String locale, Function getPattern) : _locale = Intl.verifiedLocale(locale, localeExists) { _symbols = numberFormatSymbols[_locale]; _setPattern(getPattern(_symbols)); } /** * Return the locale code in which we operate, e.g. 'en_US' or 'pt'. */ String get locale => _locale; /** * Return true if the locale exists, or if it is null. The null case * is interpreted to mean that we use the default locale. */ static bool localeExists(localeName) { if (localeName == null) return false; return numberFormatSymbols.containsKey(localeName); } /** * Return the symbols which are used in our locale. Cache them to avoid * repeated lookup. */ NumberSymbols get symbols { return _symbols; } /** * Format [number] according to our pattern and return the formatted string. */ String format(num number) { // TODO(alanknight): Do we have to do anything for printing numbers bidi? // Or are they always printed left to right? if (number.isNaN) return symbols.NAN; if (number.isInfinite) return "${_signPrefix(number)}${symbols.INFINITY}"; _newBuffer(); _add(_signPrefix(number)); _formatNumber(number.abs() * _multiplier); _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); } else { _formatFixed(number); } } /** Format the number in exponential notation. */ void _formatExponential(num number) { if (number == 0.0) { _formatFixed(number); _formatExponent(0); return; } var exponent = (log(number) / log(10)).floor(); var mantissa = number / pow(10.0, exponent); var minIntDigits = minimumIntegerDigits; if (maximumIntegerDigits > 1 && maximumIntegerDigits > minimumIntegerDigits) { // A repeating range is defined; adjust to it as follows. // If repeat == 3, we have 6,5,4=>3; 3,2,1=>0; 0,-1,-2=>-3; // -3,-4,-5=>-6, etc. This takes into account that the // exponent we have here is off by one from what we expect; // it is for the format 0.MMMMMx10^n. while ((exponent % maximumIntegerDigits) != 0) { mantissa *= 10; exponent--; } minIntDigits = 1; } else { // No repeating range is defined, use minimum integer digits. if (minimumIntegerDigits < 1) { exponent++; mantissa /= 10; } else { exponent -= minimumIntegerDigits - 1; mantissa *= pow(10, minimumIntegerDigits - 1); } } _formatFixed(mantissa); _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()); } /** Used to test if we have exceeded Javascript integer limits. */ final _maxInt = pow(2, 52); /** * Format the basic number portion, inluding the fractional digits. */ void _formatFixed(num number) { // Very fussy math to get integer and fractional parts. var power = pow(10, maximumFractionDigits); var shiftedNumber = (number * power); // We must not roundToDouble() an int or it will lose precision. We must not // round() a large double or it will take its loss of precision and // preserve it in an int, which we will then print to the right // of the decimal place. Therefore, only roundToDouble if we are already // a double. if (shiftedNumber is double) { shiftedNumber = shiftedNumber.roundToDouble(); } var intValue, fracValue; if (shiftedNumber.isInfinite) { intValue = number.toInt(); fracValue = 0; } else { intValue = shiftedNumber.round() ~/ power; fracValue = (shiftedNumber - intValue * power).floor(); } var fractionPresent = minimumFractionDigits > 0 || fracValue > 0; // If the int part is larger than 2^52 and we're on Javascript (so it's // really a float) it will lose precision, so pad out the rest of it // with zeros. Check for Javascript by seeing if an integer is double. var paddingDigits = new StringBuffer(); if (1 is double && intValue > _maxInt) { var howManyDigitsTooBig = (log(intValue) / LN10).ceil() - 16; var divisor = pow(10, howManyDigitsTooBig).round(); for (var each in new List(howManyDigitsTooBig.toInt())) { paddingDigits.write(symbols.ZERO_DIGIT); } intValue = (intValue / divisor).truncate(); } var integerDigits = "${intValue}${paddingDigits}".codeUnits; var digitLength = integerDigits.length; if (_hasPrintableIntegerPart(intValue)) { _pad(minimumIntegerDigits - digitLength); for (var i = 0; i < digitLength; i++) { _addDigit(integerDigits[i]); _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.codeUnits; var fractionLength = fractionPart.length; while(fractionCodes[fractionLength - 1] == _zero && 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) { return intValue > 0 || 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(); } /** A group of methods that provide support for writing digits and other * required characters into [_buffer] easily. */ void _add(String x) { _buffer.write(x);} void _addCharCode(int x) { _buffer.writeCharCode(x); } void _addZero() { _buffer.write(symbols.ZERO_DIGIT); } void _addDigit(int x) { _buffer.writeCharCode(_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.codeUnits) { _addDigit(x); } } /** * We are printing the digits of the number from left to right. We may need * to print a thousands separator or other grouping character as appropriate * to the locale. So we find how many places we are from the end of the number * by subtracting our current [position] from the [totalLength] and print * the separator character every [_groupingSize] digits. */ void _group(int totalLength, int position) { var distanceFromEnd = totalLength - position; if (distanceFromEnd <= 1 || _groupingSize <= 0) return; if (distanceFromEnd % _groupingSize == 1) { _add(symbols.GROUP_SEP); } } /** Returns the code point for the character '0'. */ final _zero = '0'.codeUnits.first; /** Returns the code point for the locale's zero digit. */ // Note that there is a slight risk of a locale's zero digit not fitting // into a single code unit, but it seems very unlikely, and if it did, // there's a pretty good chance that our assumptions about being able to do // arithmetic on it would also be invalid. get _localeZero => symbols.ZERO_DIGIT.codeUnits.first; /** * Returns the prefix for [x] based on whether it's positive or negative. * 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; } void _setPattern(String newPattern) { if (newPattern == null) return; // Make spaces non-breaking _pattern = newPattern.replaceAll(' ', '\u00a0'); var parser = new _NumberFormatParser(this, newPattern); parser.parse(); } String toString() => "NumberFormat($_locale, $_pattern)"; }
Static Methods
bool localeExists(localeName) #
Return true if the locale exists, or if it is null. The null case is interpreted to mean that we use the default locale.
static bool localeExists(localeName) { if (localeName == null) return false; return numberFormatSymbols.containsKey(localeName); }
Constructors
factory NumberFormat([String newPattern, String locale]) #
Create a number format that prints using newPattern as it applies in locale.
factory NumberFormat([String newPattern, String locale]) { return new NumberFormat._forPattern(locale, (x) => newPattern); }
new NumberFormat.currencyPattern([String locale]) #
Create a number format that prints as CURRENCY_PATTERN.
NumberFormat.currencyPattern([String locale]) : this._forPattern(locale, (x) => x.CURRENCY_PATTERN);
new NumberFormat.decimalPattern([String locale]) #
Create a number format that prints as DECIMAL_PATTERN.
NumberFormat.decimalPattern([String locale]) : this._forPattern(locale, (x) => x.DECIMAL_PATTERN);
new NumberFormat.percentPattern([String locale]) #
Create a number format that prints as PERCENT_PATTERN.
NumberFormat.percentPattern([String locale]) : this._forPattern(locale, (x) => x.PERCENT_PATTERN);
new NumberFormat.scientificPattern([String locale]) #
Create a number format that prints as SCIENTIFIC_PATTERN.
NumberFormat.scientificPattern([String locale]) : this._forPattern(locale, (x) => x.SCIENTIFIC_PATTERN);
Properties
final String locale #
Return the locale code in which we operate, e.g. 'en_US' or 'pt'.
String get locale => _locale;
int maximumFractionDigits #
int maximumFractionDigits = 3
int maximumIntegerDigits #
int maximumIntegerDigits = 40
int minimumExponentDigits #
int minimumExponentDigits = 0
int minimumFractionDigits #
int minimumFractionDigits = 0
int minimumIntegerDigits #
int minimumIntegerDigits = 1
final NumberSymbols symbols #
Return the symbols which are used in our locale. Cache them to avoid repeated lookup.
NumberSymbols get symbols { return _symbols; }
Methods
String format(num number) #
Format number according to our pattern and return the formatted string.
String format(num number) { // TODO(alanknight): Do we have to do anything for printing numbers bidi? // Or are they always printed left to right? if (number.isNaN) return symbols.NAN; if (number.isInfinite) return "${_signPrefix(number)}${symbols.INFINITY}"; _newBuffer(); _add(_signPrefix(number)); _formatNumber(number.abs() * _multiplier); _add(_signSuffix(number)); var result = _buffer.toString(); _buffer = null; return result; }
String toString() #
Returns a string representation of this object.
String toString() => "NumberFormat($_locale, $_pattern)";