| // 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. |
| |
| import 'dart:math'; |
| |
| import 'package:intl/number_symbols.dart'; |
| import 'package:intl/number_symbols_data.dart'; |
| import 'package:intl/src/intl_helpers.dart' as helpers; |
| import 'package:intl/src/plural_rules.dart' as plural_rules; |
| |
| import 'constants.dart' as constants; |
| import 'number_format_parser.dart'; |
| import 'number_parser.dart'; |
| |
| part 'compact_number_format.dart'; |
| |
| // ignore_for_file: constant_identifier_names |
| |
| /// The function that we pass internally to NumberFormat to get |
| /// the appropriate pattern (e.g. currency) |
| typedef _PatternGetter = String? Function(NumberSymbols); |
| |
| /// 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, to say 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 present) |
| /// |
| /// For example, |
| /// |
| /// var f = 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 percent = NumberFormat.percentPattern("ar"); var |
| /// eurosInUSFormat = NumberFormat.currency(locale: "en_US", |
| /// symbol: "€"); |
| /// |
| /// There are several such constructors available, though some of them are |
| /// limited. For example, at the moment, scientificPattern prints only as |
| /// equivalent to "#E0" and does not take into account significant digits. |
| class NumberFormat { |
| /// Variables to determine how number printing behaves. |
| final String negativePrefix; |
| final String positivePrefix; |
| final String negativeSuffix; |
| final 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; |
| |
| /// In some formats the last grouping size may be different than previous |
| /// ones, e.g. Hindi. |
| int _finalGroupingSize; |
| |
| /// Set to true if the format has explicitly set the grouping size. |
| final bool _decimalSeparatorAlwaysShown; |
| final bool _useSignForPositiveExponent; |
| final bool _useExponentialNotation; |
| |
| /// Explicitly store if we are a currency format, and so should use the |
| /// appropriate number of decimal digits for a currency. |
| // TODO(alanknight): Handle currency formats which are specified in a raw |
| /// pattern, not using one of the currency constructors. |
| final bool _isForCurrency; |
| |
| int maximumIntegerDigits; |
| int minimumIntegerDigits; |
| |
| bool _explicitMaximumFractionDigits = false; |
| int _maximumFractionDigits; |
| int get maximumFractionDigits => _maximumFractionDigits; |
| set maximumFractionDigits(int x) { |
| significantDigitsInUse = false; |
| _explicitMaximumFractionDigits = true; |
| _maximumFractionDigits = x; |
| _minimumFractionDigits = min(_minimumFractionDigits, x); |
| } |
| |
| bool _explicitMinimumFractionDigits = false; |
| int _minimumFractionDigits; |
| int get minimumFractionDigits => _minimumFractionDigits; |
| set minimumFractionDigits(int x) { |
| significantDigitsInUse = false; |
| _explicitMinimumFractionDigits = true; |
| _minimumFractionDigits = x; |
| _maximumFractionDigits = max(_maximumFractionDigits, x); |
| } |
| |
| int minimumExponentDigits; |
| |
| int? _maximumSignificantDigits; |
| int? get maximumSignificantDigits => _maximumSignificantDigits; |
| set maximumSignificantDigits(int? x) { |
| _maximumSignificantDigits = x; |
| if (x != null && _minimumSignificantDigits != null) { |
| _minimumSignificantDigits = min(_minimumSignificantDigits!, x); |
| } |
| significantDigitsInUse = true; |
| } |
| |
| /// Whether minimumSignificantDigits should cause trailing 0 in fraction part. |
| /// |
| /// Ex: with 2 significant digits: |
| /// 0.999 => "1.0" (strict) or "1" (non-strict). |
| bool minimumSignificantDigitsStrict = false; |
| |
| int? _minimumSignificantDigits; |
| int? get minimumSignificantDigits => _minimumSignificantDigits; |
| set minimumSignificantDigits(int? x) { |
| _minimumSignificantDigits = x; |
| if (x != null && _maximumSignificantDigits != null) { |
| _maximumSignificantDigits = max(_maximumSignificantDigits!, x); |
| } |
| significantDigitsInUse = true; |
| minimumSignificantDigitsStrict = x != null; |
| } |
| |
| /// How many significant digits should we print. |
| /// |
| /// Note that if significantDigitsInUse is the default false, this |
| /// will be ignored. |
| @Deprecated('Use maximumSignificantDigits / minimumSignificantDigits') |
| int? get significantDigits => _minimumSignificantDigits; |
| |
| set significantDigits(int? x) { |
| minimumSignificantDigits = x; |
| maximumSignificantDigits = x; |
| } |
| |
| bool significantDigitsInUse = false; |
| |
| /// For percent and permille, what are we multiplying by in order to |
| /// get the printed value, e.g. 100 for percent. |
| final int multiplier; |
| |
| /// How many digits are there in the [multiplier]. |
| final int _multiplierDigits; |
| |
| /// Stores the pattern used to create this format. This isn't used, but |
| /// is helpful in debugging. |
| final String? _pattern; |
| |
| /// The locale in which we print numbers. |
| final String _locale; |
| |
| /// Caches the symbols used for our locale. |
| final NumberSymbols _symbols; |
| |
| /// The name of the currency to print, in ISO 4217 form. |
| String? currencyName; |
| |
| /// The symbol to be used when formatting this as currency. |
| /// |
| /// For example, "$", "US$", or "€". |
| final String currencySymbol; |
| |
| /// The number of decimal places to use when formatting. |
| /// |
| /// If this is not explicitly specified in the constructor, then for |
| /// currencies we use the default value for the currency if the name is given, |
| /// otherwise we use the value from the pattern for the locale. |
| /// |
| /// So, for example, |
| /// NumberFormat.currency(name: 'USD', decimalDigits: 7) |
| /// will format with 7 decimal digits, because that's what we asked for. But |
| /// NumberFormat.currency(locale: 'en_US', name: 'JPY') |
| /// will format with zero, because that's the default for JPY, and the |
| /// currency's default takes priority over the locale's default. |
| /// NumberFormat.currency(locale: 'en_US') |
| /// will format with two, which is the default for that locale. |
| /// |
| final int? decimalDigits; |
| |
| /// 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. |
| final StringBuffer _buffer = StringBuffer(); |
| |
| /// Create a number format that prints using [newPattern] as it applies in |
| /// [locale]. |
| factory NumberFormat([String? newPattern, String? locale]) => |
| NumberFormat._forPattern(locale, (x) => newPattern); |
| |
| /// Create a number format that prints as DECIMAL_PATTERN. |
| factory NumberFormat.decimalPattern([String? locale]) => |
| NumberFormat._forPattern(locale, (x) => x.DECIMAL_PATTERN); |
| |
| /// Create a number format that prints as PERCENT_PATTERN. |
| factory NumberFormat.percentPattern([String? locale]) => |
| NumberFormat._forPattern(locale, (x) => x.PERCENT_PATTERN); |
| |
| /// Create a number format that prints as PERCENT_PATTERN. |
| factory NumberFormat.decimalPercentPattern( |
| {String? locale, int? decimalDigits}) => |
| NumberFormat._forPattern(locale, (x) => x.PERCENT_PATTERN, |
| decimalDigits: decimalDigits); |
| |
| /// Create a number format that prints as SCIENTIFIC_PATTERN. |
| factory NumberFormat.scientificPattern([String? locale]) => |
| NumberFormat._forPattern(locale, (x) => x.SCIENTIFIC_PATTERN); |
| |
| /// A regular expression to validate currency names are exactly three |
| /// alphabetic characters. |
| static final _checkCurrencyName = RegExp(r'^[a-zA-Z]{3}$'); |
| |
| /// Create a number format that prints as CURRENCY_PATTERN. (Deprecated: |
| /// prefer NumberFormat.currency) |
| /// |
| /// If provided, |
| /// use [currencyNameOrSymbol] in place of the default currency name. e.g. |
| /// var eurosInCurrentLocale = NumberFormat |
| /// .currencyPattern(Intl.defaultLocale, "€"); |
| @Deprecated('Use NumberFormat.currency') |
| factory NumberFormat.currencyPattern( |
| [String? locale, String? currencyNameOrSymbol]) { |
| // If it looks like an iso4217 name, pass as name, otherwise as symbol. |
| if (currencyNameOrSymbol != null && |
| _checkCurrencyName.hasMatch(currencyNameOrSymbol)) { |
| return NumberFormat.currency(locale: locale, name: currencyNameOrSymbol); |
| } else { |
| return NumberFormat.currency( |
| locale: locale, symbol: currencyNameOrSymbol); |
| } |
| } |
| |
| /// Create a [NumberFormat] that formats using the locale's CURRENCY_PATTERN. |
| /// |
| /// If [locale] is not specified, it will use the current default locale. |
| /// |
| /// If [name] is specified, the currency with that ISO 4217 name will be used. |
| /// Otherwise we will use the default currency name for the current locale. If |
| /// no [symbol] is specified, we will use the currency name in the formatted |
| /// result. e.g. |
| /// var f = NumberFormat.currency(locale: 'en_US', name: 'EUR') |
| /// will format currency like "EUR1.23". If we did not specify the name, it |
| /// would format like "USD1.23". |
| /// |
| /// If [symbol] is used, then that symbol will be used in formatting instead |
| /// of the name. e.g. |
| /// var eurosInCurrentLocale = NumberFormat.currency(symbol: "€"); |
| /// will format like "€1.23". Otherwise it will use the currency name. |
| /// If this is not explicitly specified in the constructor, then for |
| /// currencies we use the default value for the currency if the name is given, |
| /// otherwise we use the value from the pattern for the locale. |
| /// |
| /// If [decimalDigits] is specified, numbers will format with that many digits |
| /// after the decimal place. If it's not, they will use the default for the |
| /// currency in [name], and the default currency for [locale] if the currency |
| /// name is not specified. e.g. |
| /// NumberFormat.currency(name: 'USD', decimalDigits: 7) |
| /// will format with 7 decimal digits, because that's what we asked for. But |
| /// NumberFormat.currency(locale: 'en_US', name: 'JPY') |
| /// will format with zero, because that's the default for JPY, and the |
| /// currency's default takes priority over the locale's default. |
| /// NumberFormat.currency(locale: 'en_US') |
| /// will format with two, which is the default for that locale. |
| /// |
| /// The [customPattern] parameter can be used to specify a particular |
| /// format. This is useful if you have your own locale data which includes |
| /// unsupported formats (e.g. accounting format for currencies.) |
| // TODO(alanknight): Should we allow decimalDigits on other numbers. |
| factory NumberFormat.currency( |
| {String? locale, |
| String? name, |
| String? symbol, |
| int? decimalDigits, |
| String? customPattern}) => |
| NumberFormat._forPattern( |
| locale, (x) => customPattern ?? x.CURRENCY_PATTERN, |
| name: name, |
| currencySymbol: symbol, |
| decimalDigits: decimalDigits, |
| isForCurrency: true); |
| |
| /// Creates a [NumberFormat] for currencies, using the simple symbol for the |
| /// currency if one is available (e.g. $, €), so it should only be used if the |
| /// short currency symbol will be unambiguous. |
| /// |
| /// If [locale] is not specified, it will use the current default locale. |
| /// |
| /// If [name] is specified, the currency with that ISO 4217 name will be used. |
| /// Otherwise we will use the default currency name for the current locale. We |
| /// will assume that the symbol for this is well known in the locale and |
| /// unambiguous. If you format CAD in an en_US locale using this format it |
| /// will display as "$", which may be confusing to the user. |
| /// |
| /// If [decimalDigits] is specified, numbers will format with that many digits |
| /// after the decimal place. If it's not, they will use the default for the |
| /// currency in [name], and the default currency for [locale] if the currency |
| /// name is not specified. e.g. |
| /// NumberFormat.simpleCurrency(name: 'USD', decimalDigits: 7) |
| /// will format with 7 decimal digits, because that's what we asked for. But |
| /// NumberFormat.simpleCurrency(locale: 'en_US', name: 'JPY') |
| /// will format with zero, because that's the default for JPY, and the |
| /// currency's default takes priority over the locale's default. |
| /// NumberFormat.simpleCurrency(locale: 'en_US') |
| /// will format with two, which is the default for that locale. |
| factory NumberFormat.simpleCurrency( |
| {String? locale, String? name, int? decimalDigits}) { |
| return NumberFormat._forPattern(locale, (x) => x.CURRENCY_PATTERN, |
| name: name, |
| decimalDigits: decimalDigits, |
| lookupSimpleCurrencySymbol: true, |
| isForCurrency: true); |
| } |
| |
| /// Returns the simple currency symbol for given currency code, or |
| /// [currencyCode] if no simple symbol is listed. |
| /// |
| /// The simple currency symbol is generally short, and the same or related to |
| /// what is used in countries having the currency as an official symbol. It |
| /// may be a symbol character, or may have letters, or both. It may be |
| /// different according to the locale: for example, for an Arabic locale it |
| /// may consist of Arabic letters, but for a French locale consist of Latin |
| /// letters. It will not be unique: for example, "$" can appear for both USD |
| /// and CAD. |
| /// |
| /// (The current implementation is the same for all locales, but this is |
| /// temporary and callers shouldn't rely on it.) |
| String simpleCurrencySymbol(String currencyCode) => |
| constants.simpleCurrencySymbols[currencyCode] ?? currencyCode; |
| |
| /// Create a number format that prints in a pattern we get from |
| /// the [getPattern] function using the locale [locale]. |
| /// |
| /// The [currencySymbol] can either be specified directly, or we can pass a |
| /// function [computeCurrencySymbol] that will compute it later, given other |
| /// information, typically the verified locale. |
| factory NumberFormat._forPattern(String? locale, _PatternGetter getPattern, |
| {String? name, |
| String? currencySymbol, |
| int? decimalDigits, |
| bool lookupSimpleCurrencySymbol = false, |
| bool isForCurrency = false}) { |
| locale = helpers.verifiedLocale(locale, localeExists, null)!; |
| var symbols = numberFormatSymbols[locale] as NumberSymbols; |
| var localeZero = symbols.ZERO_DIGIT.codeUnitAt(0); |
| var zeroOffset = localeZero - constants.asciiZeroCodeUnit; |
| name ??= symbols.DEF_CURRENCY_CODE; |
| if (currencySymbol == null && lookupSimpleCurrencySymbol) { |
| currencySymbol = constants.simpleCurrencySymbols[name]; |
| } |
| currencySymbol ??= name; |
| |
| var pattern = getPattern(symbols); |
| |
| return NumberFormat._( |
| name, |
| currencySymbol, |
| isForCurrency, |
| locale, |
| localeZero, |
| pattern, |
| symbols, |
| zeroOffset, |
| NumberFormatParser.parse(symbols, pattern, isForCurrency, |
| currencySymbol, name, decimalDigits)); |
| } |
| |
| NumberFormat._( |
| this.currencyName, |
| this.currencySymbol, |
| this._isForCurrency, |
| this._locale, |
| this.localeZero, |
| this._pattern, |
| this._symbols, |
| this._zeroOffset, |
| NumberFormatParseResult result) |
| : positivePrefix = result.positivePrefix, |
| negativePrefix = result.negativePrefix, |
| positiveSuffix = result.positiveSuffix, |
| negativeSuffix = result.negativeSuffix, |
| multiplier = result.multiplier, |
| _multiplierDigits = result.multiplierDigits, |
| _useExponentialNotation = result.useExponentialNotation, |
| minimumExponentDigits = result.minimumExponentDigits, |
| maximumIntegerDigits = result.maximumIntegerDigits, |
| minimumIntegerDigits = result.minimumIntegerDigits, |
| _maximumFractionDigits = result.maximumFractionDigits, |
| _minimumFractionDigits = result.minimumFractionDigits, |
| _groupingSize = result.groupingSize, |
| _finalGroupingSize = result.finalGroupingSize, |
| _useSignForPositiveExponent = result.useSignForPositiveExponent, |
| _decimalSeparatorAlwaysShown = result.decimalSeparatorAlwaysShown, |
| decimalDigits = result.decimalDigits; |
| |
| /// A number format for compact representations, e.g. "1.2M" instead |
| /// of "1,200,000". |
| factory NumberFormat.compact({String? locale, bool explicitSign = false}) { |
| return _CompactNumberFormat( |
| locale: locale, |
| formatType: _CompactFormatType.COMPACT_DECIMAL_SHORT_PATTERN, |
| explicitSign: explicitSign); |
| } |
| |
| /// A number format for "long" compact representations, e.g. "1.2 million" |
| /// instead of "1,200,000". |
| factory NumberFormat.compactLong( |
| {String? locale, bool explicitSign = false}) { |
| return _CompactNumberFormat( |
| locale: locale, |
| formatType: _CompactFormatType.COMPACT_DECIMAL_LONG_PATTERN, |
| explicitSign: explicitSign); |
| } |
| |
| /// A number format for compact currency representations, e.g. "$1.2M" instead |
| /// of "$1,200,000", and which will automatically determine a currency symbol |
| /// based on the currency name or the locale. See |
| /// [NumberFormat.simpleCurrency]. |
| factory NumberFormat.compactSimpleCurrency( |
| {String? locale, String? name, int? decimalDigits}) { |
| return _CompactNumberFormat( |
| locale: locale, |
| formatType: _CompactFormatType.COMPACT_DECIMAL_SHORT_CURRENCY_PATTERN, |
| name: name, |
| getPattern: (symbols) => symbols.CURRENCY_PATTERN, |
| decimalDigits: decimalDigits, |
| lookupSimpleCurrencySymbol: true, |
| isForCurrency: true); |
| } |
| |
| /// A number format for compact currency representations, e.g. "$1.2M" instead |
| /// of "$1,200,000". |
| factory NumberFormat.compactCurrency( |
| {String? locale, String? name, String? symbol, int? decimalDigits}) { |
| return _CompactNumberFormat( |
| locale: locale, |
| formatType: _CompactFormatType.COMPACT_DECIMAL_SHORT_CURRENCY_PATTERN, |
| name: name, |
| getPattern: (symbols) => symbols.CURRENCY_PATTERN, |
| currencySymbol: symbol, |
| decimalDigits: decimalDigits, |
| isForCurrency: true); |
| } |
| |
| /// 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(String? 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 => _symbols; |
| |
| /// Format [number] according to our pattern and return the formatted string. |
| String format(dynamic number) { |
| if (_isNaN(number)) return symbols.NAN; |
| if (_isInfinite(number)) return '${_signPrefix(number)}${symbols.INFINITY}'; |
| |
| _add(_signPrefix(number)); |
| _formatNumber(number.abs()); |
| _add(_signSuffix(number)); |
| |
| var result = _buffer.toString(); |
| _buffer.clear(); |
| return result; |
| } |
| |
| /// Parse the number represented by the string. If it's not |
| /// parseable, throws a [FormatException]. |
| num parse(String text) => NumberParser(this, text).value!; |
| |
| /// Format the main part of the number in the form dictated by the pattern. |
| void _formatNumber(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) / _ln10).floor(); |
| var mantissa = number / pow(10.0, exponent); |
| |
| 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--; |
| } |
| } 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 integer limits. |
| // TODO(alanknight): Do we have a MaxInt constant we could use instead? |
| static final _maxInt = 1 is double ? pow(2, 52) : 1.0e300.floor(); |
| static final _maxDigits = (log(_maxInt) / log(10)).ceil(); |
| |
| /// Helpers to check numbers that don't conform to the [num] interface, |
| /// e.g. Int64 |
| bool _isInfinite(number) => number is num ? number.isInfinite : false; |
| bool _isNaN(number) => number is num ? number.isNaN : false; |
| |
| /// Helper to get the floor of a number which might not be num. This should |
| /// only ever be called with an argument which is positive, or whose abs() |
| /// is negative. The second case is the maximum negative value on a |
| /// fixed-length integer. Since they are integers, they are also their own |
| /// floor. |
| dynamic _floor(dynamic number) { |
| if (number.isNegative && !number.abs().isNegative) { |
| throw ArgumentError( |
| 'Internal error: expected positive number, got $number'); |
| } |
| return (number is num) ? number.floor() : number ~/ 1; |
| } |
| |
| /// Helper to round a number which might not be num. |
| dynamic _round(dynamic number) { |
| if (number is num) { |
| if (number.isInfinite) { |
| return _maxInt; |
| } else { |
| return number.round(); |
| } |
| } else if (number.remainder(1) == 0) { |
| // Not a normal number, but int-like, e.g. Int64 |
| return number; |
| } else { |
| // TODO(alanknight): Do this more efficiently. If IntX had floor and |
| // round we could avoid this. |
| var basic = _floor(number); |
| var fraction = (number - basic).toDouble().round(); |
| return fraction == 0 ? number : number + fraction; |
| } |
| } |
| |
| // Return the number of digits left of the decimal place in [number]. |
| static int numberOfIntegerDigits(dynamic number) { |
| var simpleNumber = (number.toDouble() as double).abs(); |
| // It's unfortunate that we have to do this, but we get precision errors |
| // that affect the result if we use logs, e.g. 1000000 |
| if (simpleNumber < 10) return 1; |
| if (simpleNumber < 100) return 2; |
| if (simpleNumber < 1000) return 3; |
| if (simpleNumber < 10000) return 4; |
| if (simpleNumber < 100000) return 5; |
| if (simpleNumber < 1000000) return 6; |
| if (simpleNumber < 10000000) return 7; |
| if (simpleNumber < 100000000) return 8; |
| if (simpleNumber < 1000000000) return 9; |
| if (simpleNumber < 10000000000) return 10; |
| if (simpleNumber < 100000000000) return 11; |
| if (simpleNumber < 1000000000000) return 12; |
| if (simpleNumber < 10000000000000) return 13; |
| if (simpleNumber < 100000000000000) return 14; |
| if (simpleNumber < 1000000000000000) return 15; |
| if (simpleNumber < 10000000000000000) return 16; |
| // We're past the point where being off by one on the number of digits |
| // will affect the pattern, so now we can use logs. |
| return max(1, (log(simpleNumber) / _ln10).ceil()); |
| } |
| |
| /// Whether to use SignificantDigits unconditionally for fraction digits. |
| bool _useDefaultSignificantDigits() => !_isForCurrency; |
| |
| /// How many digits after the decimal place should we display, given that |
| /// by default, [fractionDigits] should be used, and there are up to |
| /// [expectedSignificantDigits] left to display in the fractional part.. |
| int _adjustFractionDigits(int fractionDigits, expectedSignificantDigits) { |
| if (_useDefaultSignificantDigits()) return fractionDigits; |
| // If we are printing a currency significant digits would have us only print |
| // some of the decimal digits, use all of them. So $12.30, not $12.3 |
| if (expectedSignificantDigits > 0) { |
| return decimalDigits!; |
| } else { |
| return min(fractionDigits, decimalDigits!); |
| } |
| } |
| |
| /// Format the basic number portion, including the fractional digits. |
| void _formatFixed(dynamic number) { |
| dynamic integerPart; |
| int fractionPart; |
| int extraIntegerDigits; |
| var fractionDigits = maximumFractionDigits; |
| var minFractionDigits = minimumFractionDigits; |
| |
| var power = 0; |
| int digitMultiplier; |
| |
| if (_isInfinite(number)) { |
| integerPart = number.toInt(); |
| extraIntegerDigits = 0; |
| fractionPart = 0; |
| } else { |
| // We have three possible pieces. First, the basic integer part. If this |
| // is a percent or permille, the additional 2 or 3 digits. Finally the |
| // fractional part. |
| // We avoid multiplying the number because it might overflow if we have |
| // a fixed-size integer type, so we extract each of the three as an |
| // integer pieces. |
| integerPart = _floor(number); |
| var fraction = number - integerPart; |
| if (fraction.toInt() != 0) { |
| // If the fractional part leftover is > 1, presumbly the number |
| // was too big for a fixed-size integer, so leave it as whatever |
| // it was - the obvious thing is a double. |
| integerPart = number; |
| fraction = 0; |
| } |
| |
| /// If we have significant digits, compute the number of fraction |
| /// digits based on that. |
| void computeFractionDigits() { |
| if (significantDigitsInUse) { |
| var integerLength = number == 0 |
| ? 1 |
| : integerPart != 0 |
| ? numberOfIntegerDigits(integerPart) |
| // We might need to add digits after decimal point. |
| : (log(fraction) / ln10).ceil(); |
| |
| if (minimumSignificantDigits != null) { |
| var remainingSignificantDigits = |
| minimumSignificantDigits! - _multiplierDigits - integerLength; |
| |
| fractionDigits = max(0, remainingSignificantDigits); |
| if (minimumSignificantDigitsStrict) { |
| minFractionDigits = fractionDigits; |
| } |
| fractionDigits = _adjustFractionDigits( |
| fractionDigits, remainingSignificantDigits); |
| } |
| |
| if (maximumSignificantDigits != null) { |
| if (maximumSignificantDigits! == 0) { |
| // Stupid case: only '0' has no significant digits. |
| integerPart = 0; |
| fractionDigits = 0; |
| } else if (maximumSignificantDigits! < |
| integerLength + _multiplierDigits) { |
| // We may have to round. |
| var divideBy = pow(10, integerLength - maximumSignificantDigits!); |
| if (maximumSignificantDigits! < integerLength) { |
| integerPart = (integerPart / divideBy).round() * divideBy; |
| } |
| fraction = (fraction / divideBy).round() * divideBy; |
| fractionDigits = 0; |
| } else { |
| fractionDigits = |
| maximumSignificantDigits! - integerLength - _multiplierDigits; |
| fractionDigits = |
| _adjustFractionDigits(fractionDigits, fractionDigits); |
| } |
| } |
| if (fractionDigits > maximumFractionDigits && |
| _explicitMaximumFractionDigits) { |
| fractionDigits = min(fractionDigits, maximumFractionDigits); |
| } |
| if (fractionDigits < minimumFractionDigits && |
| _explicitMinimumFractionDigits) { |
| fractionDigits = _minimumFractionDigits; |
| } |
| } |
| } |
| |
| computeFractionDigits(); |
| |
| power = pow(10, fractionDigits) as int; |
| digitMultiplier = power * multiplier; |
| |
| // Multiply out to the number of decimal places and the percent, then |
| // round. For fixed-size integer types this should always be zero, so |
| // multiplying is OK. |
| var remainingDigits = _round(fraction * digitMultiplier).toInt(); |
| |
| var hasRounding = false; |
| if (remainingDigits >= digitMultiplier) { |
| // Overflow into the main digits: 0.99 => 1.00 |
| integerPart++; |
| remainingDigits -= digitMultiplier; |
| hasRounding = true; |
| } else if (numberOfIntegerDigits(remainingDigits) > |
| numberOfIntegerDigits(_floor(fraction * digitMultiplier).toInt())) { |
| // Fraction has been rounded (0.0996 -> 0.1). |
| fraction = remainingDigits / digitMultiplier; |
| hasRounding = true; |
| } |
| if (hasRounding && significantDigitsInUse) { |
| // We might have to recompute significant digits after fraction. |
| // With 3 significant digits, "9.999" should be "10.0", not "10.00". |
| computeFractionDigits(); |
| } |
| |
| // Separate out the extra integer parts from the fraction part. |
| extraIntegerDigits = remainingDigits ~/ power; |
| fractionPart = remainingDigits % power; |
| } |
| |
| var integerDigits = _integerDigits(integerPart, extraIntegerDigits); |
| var digitLength = integerDigits.length; |
| var fractionPresent = |
| fractionDigits > 0 && (minFractionDigits > 0 || fractionPart > 0); |
| |
| if (_hasIntegerDigits(integerDigits)) { |
| // Add the padding digits to the regular digits so that we get grouping. |
| var padding = '0' * (minimumIntegerDigits - digitLength); |
| integerDigits = '$padding$integerDigits'; |
| digitLength = integerDigits.length; |
| for (var i = 0; i < digitLength; i++) { |
| _addDigit(integerDigits.codeUnitAt(i)); |
| _group(digitLength, i); |
| } |
| } else if (!fractionPresent) { |
| // If neither fraction nor integer part exists, just print zero. |
| _addZero(); |
| } |
| |
| _decimalSeparator(fractionPresent); |
| if (fractionPresent) { |
| _formatFractionPart((fractionPart + power).toString(), minFractionDigits); |
| } |
| } |
| |
| /// Compute the raw integer digits which will then be printed with |
| /// grouping and translated to localized digits. |
| String _integerDigits(integerPart, extraIntegerDigits) { |
| // If the integer part is larger than the maximum integer size |
| // (2^52 on Javascript, 2^63 on the VM) it will lose precision, |
| // so pad out the rest of it with zeros. |
| var paddingDigits = ''; |
| if (integerPart is num && integerPart > _maxInt) { |
| var howManyDigitsTooBig = (log(integerPart) / _ln10).ceil() - _maxDigits; |
| num divisor = pow(10, howManyDigitsTooBig).round(); |
| // pow() produces 0 if the result is too large for a 64-bit int. |
| // If that happens, use a floating point divisor instead. |
| if (divisor == 0) divisor = pow(10.0, howManyDigitsTooBig); |
| paddingDigits = '0' * howManyDigitsTooBig.toInt(); |
| integerPart = (integerPart / divisor).truncate(); |
| } |
| |
| var extra = extraIntegerDigits == 0 ? '' : extraIntegerDigits.toString(); |
| var intDigits = _mainIntegerDigits(integerPart); |
| var paddedExtra = |
| intDigits.isEmpty ? extra : extra.padLeft(_multiplierDigits, '0'); |
| return '$intDigits$paddedExtra$paddingDigits'; |
| } |
| |
| /// The digit string of the integer part. This is the empty string if the |
| /// integer part is zero and otherwise is the toString() of the integer |
| /// part, stripping off any minus sign. |
| String _mainIntegerDigits(integer) { |
| if (integer == 0) return ''; |
| var digits = integer.toString(); |
| if (significantDigitsInUse && |
| maximumSignificantDigits != null && |
| digits.length > maximumSignificantDigits!) { |
| digits = digits.substring(0, maximumSignificantDigits!) + |
| ''.padLeft(digits.length - maximumSignificantDigits!, '0'); |
| } |
| // If we have a fixed-length int representation, it can have a negative |
| // number whose negation is also negative, e.g. 2^-63 in 64-bit. |
| // Remove the minus sign. |
| return digits.startsWith('-') ? digits.substring(1) : digits; |
| } |
| |
| /// Format the part after the decimal place in a fixed point number. |
| void _formatFractionPart(String fractionPart, int minDigits) { |
| var fractionLength = fractionPart.length; |
| while (fractionPart.codeUnitAt(fractionLength - 1) == |
| constants.asciiZeroCodeUnit && |
| fractionLength > minDigits + 1) { |
| fractionLength--; |
| } |
| for (var i = 1; i < fractionLength; i++) { |
| _addDigit(fractionPart.codeUnitAt(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 (this may include digits |
| /// which have been moved left because of percent or permille formatting), |
| /// or because the minimum number of printable digits is greater than 1. |
| bool _hasIntegerDigits(String digits) => |
| digits.isNotEmpty || minimumIntegerDigits > 0; |
| |
| /// 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 _addZero() { |
| _buffer.write(symbols.ZERO_DIGIT); |
| } |
| |
| void _addDigit(int x) { |
| _buffer.writeCharCode(x + _zeroOffset); |
| } |
| |
| void _pad(int numberOfDigits, String basic) { |
| if (_zeroOffset == 0) { |
| _buffer.write(basic.padLeft(numberOfDigits, '0')); |
| } else { |
| _slowPad(numberOfDigits, basic); |
| } |
| } |
| |
| /// Print padding up to [numberOfDigits] above what's included in [basic]. |
| void _slowPad(int numberOfDigits, String basic) { |
| for (var i = 0; i < numberOfDigits - basic.length; i++) { |
| _add(symbols.ZERO_DIGIT); |
| } |
| for (var i = 0; i < basic.length; i++) { |
| _addDigit(basic.codeUnitAt(i)); |
| } |
| } |
| |
| /// 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 printing |
| /// the separator character every [_groupingSize] digits, with the final |
| /// grouping possibly being of a different size, [_finalGroupingSize]. |
| void _group(int totalLength, int position) { |
| var distanceFromEnd = totalLength - position; |
| if (distanceFromEnd <= 1 || _groupingSize <= 0) return; |
| if (distanceFromEnd == _finalGroupingSize + 1) { |
| _add(symbols.GROUP_SEP); |
| } else if ((distanceFromEnd > _finalGroupingSize) && |
| (distanceFromEnd - _finalGroupingSize) % _groupingSize == 1) { |
| _add(symbols.GROUP_SEP); |
| } |
| } |
| |
| /// The code point for the locale's zero digit. |
| /// |
| /// Initialized when the locale is set. |
| final int localeZero; |
| |
| /// The difference between our zero and '0'. |
| /// |
| /// In other words, a constant _localeZero - _zero. Initialized when |
| /// the locale is set. |
| final int _zeroOffset; |
| |
| /// Returns the prefix for [x] based on whether it's positive or negative. |
| /// In en_US this would be '' and '-' respectively. |
| String _signPrefix(x) => 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(x) => x.isNegative ? negativeSuffix : positiveSuffix; |
| |
| /// Explicitly turn off any grouping (e.g. by thousands) in this format. |
| /// |
| /// This is used in compact number formatting, where we |
| /// omit the normal grouping. Best to know what you're doing if you call it. |
| void turnOffGrouping() { |
| _groupingSize = 0; |
| _finalGroupingSize = 0; |
| } |
| |
| @override |
| String toString() => 'NumberFormat($_locale, $_pattern)'; |
| } |
| |
| final _ln10 = log(10); |