Index: pkg/intl/lib/src/intl/number_format.dart |
diff --git a/pkg/intl/lib/src/intl/number_format.dart b/pkg/intl/lib/src/intl/number_format.dart |
index 377db7dea10689800e8814470ed61959b2491ced..2e23d358e7947d871e761cb063ff613886cc5b42 100644 |
--- a/pkg/intl/lib/src/intl/number_format.dart |
+++ b/pkg/intl/lib/src/intl/number_format.dart |
@@ -71,7 +71,19 @@ class NumberFormat { |
int minimumFractionDigits = 0; |
int minimumExponentDigits = 0; |
- int _multiplier = 1; |
+ /** |
+ * For percent and permille, what are we multiplying by in order to |
+ * get the printed value, e.g. 100 for percent. |
+ */ |
+ int get _multiplier => _internalMultiplier; |
+ set _multiplier(int x) { |
+ _internalMultiplier = x; |
+ _multiplierDigits = (log(_multiplier) / LN10).round(); |
+ } |
+ int _internalMultiplier = 1; |
+ |
+ /** How many digits are there in the [_multiplier]. */ |
+ int _multiplierDigits = 0; |
/** |
* Stores the pattern used to create this format. This isn't used, but |
@@ -162,14 +174,12 @@ class NumberFormat { |
/** |
* 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}"; |
+ String format(number) { |
+ if (_isNaN(number)) return symbols.NAN; |
+ if (_isInfinite(number)) return "${_signPrefix(number)}${symbols.INFINITY}"; |
_add(_signPrefix(number)); |
- _formatNumber(number.abs() * _multiplier); |
+ _formatNumber(number.abs()); |
_add(_signSuffix(number)); |
var result = _buffer.toString(); |
@@ -186,7 +196,7 @@ class NumberFormat { |
/** |
* Format the main part of the number in the form dictated by the pattern. |
*/ |
- void _formatNumber(num number) { |
+ void _formatNumber(number) { |
if (_useExponentialNotation) { |
_formatExponential(number); |
} else { |
@@ -250,48 +260,60 @@ class NumberFormat { |
final _maxInt = pow(2, 52); |
/** |
+ * Helpers to check numbers that don't conform to the [num] interface, |
+ * e.g. Int64 |
+ */ |
+ _isInfinite(number) => number is num ? number.isInfinite : false; |
+ _isNaN(number) => number is num ? number.isNaN : false; |
+ _round(number) => number is num ? number.round() : number; |
+ _floor(number) => number is num ? number.floor() : number; |
+ |
+ /** |
* 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; |
+ void _formatFixed(number) { |
+ var integerPart; |
+ int fractionPart; |
+ int extraIntegerDigits; |
+ |
+ final power = pow(10, maximumFractionDigits); |
+ final digitMultiplier = power * _multiplier; |
+ |
+ if (_isInfinite(number)) { |
+ integerPart = number.toInt(); |
+ extraIntegerDigits = 0; |
+ fractionPart = 0; |
} else { |
- intValue = shiftedNumber.round() ~/ power; |
- fracValue = (shiftedNumber - intValue * power).floor(); |
+ // 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; |
+ // 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(); |
+ // However, in rounding we may overflow into the main digits. |
+ if (remainingDigits >= digitMultiplier) { |
+ integerPart++; |
+ remainingDigits -= digitMultiplier; |
+ } |
+ // Separate out the extra integer parts from the fraction part. |
+ extraIntegerDigits = remainingDigits ~/ power; |
+ fractionPart = remainingDigits % power; |
} |
- var fractionPresent = minimumFractionDigits > 0 || fracValue > 0; |
+ var fractionPresent = minimumFractionDigits > 0 || fractionPart > 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 = ''; |
- if (1 is double && intValue > _maxInt) { |
- var howManyDigitsTooBig = (log(intValue) / LN10).ceil() - 16; |
- var divisor = pow(10, howManyDigitsTooBig).round(); |
- paddingDigits = symbols.ZERO_DIGIT * howManyDigitsTooBig.toInt(); |
- |
- intValue = (intValue / divisor).truncate(); |
- } |
- var integerDigits = "${intValue}${paddingDigits}".codeUnits; |
+ var integerDigits = _integerDigits(integerPart, extraIntegerDigits); |
var digitLength = integerDigits.length; |
- if (_hasPrintableIntegerPart(intValue)) { |
+ if (_hasPrintableIntegerPart(integerPart)) { |
_pad(minimumIntegerDigits - digitLength); |
for (var i = 0; i < digitLength; i++) { |
- _addDigit(integerDigits[i]); |
+ _addDigit(integerDigits.codeUnitAt(i)); |
_group(digitLength, i); |
} |
} else if (!fractionPresent) { |
@@ -300,7 +322,44 @@ class NumberFormat { |
} |
_decimalSeparator(fractionPresent); |
- _formatFractionPart((fracValue + power).toString()); |
+ _formatFractionPart((fractionPart + power).toString()); |
+ } |
+ |
+ /** |
+ * Compute the raw integer digits which will then be printed with |
+ * grouping and translated to localized digits. |
+ */ |
+ String _integerDigits(integerPart, extraIntegerDigits) { |
+ // 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 = ''; |
+ if (1 is double && integerPart is num && integerPart > _maxInt) { |
+ var howManyDigitsTooBig = (log(integerPart) / LN10).ceil() - 16; |
+ var divisor = pow(10, howManyDigitsTooBig).round(); |
+ paddingDigits = symbols.ZERO_DIGIT * 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 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; |
} |
/** |
@@ -330,8 +389,8 @@ class NumberFormat { |
* 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) => |
- intValue > 0 || minimumIntegerDigits > 0; |
+ bool _hasPrintableIntegerPart(x) => |
+ x > 0 || minimumIntegerDigits > 0; |
/** A group of methods that provide support for writing digits and other |
* required characters into [_buffer] easily. |
@@ -384,13 +443,13 @@ class NumberFormat { |
* 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) => x.isNegative ? _negativePrefix : _positivePrefix; |
+ 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(num x) => x.isNegative ? _negativeSuffix : _positiveSuffix; |
+ String _signSuffix(x) => x.isNegative ? _negativeSuffix : _positiveSuffix; |
void _setPattern(String newPattern) { |
if (newPattern == null) return; |