Dart DocumentationintlNumberFormat

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 separator
  • E 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.

docs inherited from Object
String toString() => "NumberFormat($_locale, $_pattern)";