Allow Intl to handle scaled fixed-point values
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=110469094
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cf9375d..183bf93 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,10 +1,12 @@
 ## 0.12.5
   * Parse Eras in DateFormat.
-  * Update pubspec.yaml to allow newer version of fixnum.
+  * Update pubspec.yaml to allow newer version of fixnum and analyzer.
   * Improvements to the compiled size of generated messages code with dart2js.
   * Allow adjacent literal strings to be used for message names/descriptions.
   * Provide a better error message for some cases of bad parameters
     to plural/gender/select messages.
+  * Introduce a simple MicroMoney class that can represent currency values
+    scaled by a constant factor.
 
 ## 0.12.4+3
   * update analyzer to '<0.28.0' and fixnum to '<0.11.0'
diff --git a/lib/src/intl/number_format.dart b/lib/src/intl/number_format.dart
index c0e77a5..57fa07d 100644
--- a/lib/src/intl/number_format.dart
+++ b/lib/src/intl/number_format.dart
@@ -81,6 +81,7 @@
     _internalMultiplier = x;
     _multiplierDigits = (log(_multiplier) / LN10).round();
   }
+
   int _internalMultiplier = 1;
 
   /** How many digits are there in the [_multiplier]. */
@@ -264,11 +265,39 @@
    */
   _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.
+   * 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.
+   */
+  _floor(number) {
+    if (number.isNegative && !(number.abs().isNegative)) {
+      throw new 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.*/
+  _round(number) {
+    if (number is num) {
+      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;
+    }
+  }
+
+  /**
+   * Format the basic number portion, including the fractional digits.
    */
   void _formatFixed(number) {
     var integerPart;
@@ -384,23 +413,25 @@
   }
 
   /**
-   * 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.
-   */
+    * 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.
-   */
+    * 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(_localeZero + x - _zero);
   }
@@ -416,13 +447,13 @@
   }
 
   /**
-   * 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].
-   */
+    * 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;
@@ -474,7 +505,6 @@
  * then calls the system parsing methods on it.
  */
 class _NumberParser {
-
   /** The format for which we are parsing. */
   final NumberFormat format;
 
@@ -557,22 +587,22 @@
   var _replacements;
 
   Map _initializeReplacements() => {
-    symbols.DECIMAL_SEP: () => '.',
-    symbols.EXP_SYMBOL: () => 'E',
-    symbols.GROUP_SEP: handleSpace,
-    symbols.PERCENT: () {
-      scale = _NumberFormatParser._PERCENT_SCALE;
-      return '';
-    },
-    symbols.PERMILL: () {
-      scale = _NumberFormatParser._PER_MILLE_SCALE;
-      return '';
-    },
-    ' ': handleSpace,
-    '\u00a0': handleSpace,
-    '+': () => '+',
-    '-': () => '-',
-  };
+        symbols.DECIMAL_SEP: () => '.',
+        symbols.EXP_SYMBOL: () => 'E',
+        symbols.GROUP_SEP: handleSpace,
+        symbols.PERCENT: () {
+          scale = _NumberFormatParser._PERCENT_SCALE;
+          return '';
+        },
+        symbols.PERMILL: () {
+          scale = _NumberFormatParser._PER_MILLE_SCALE;
+          return '';
+        },
+        ' ': handleSpace,
+        '\u00a0': handleSpace,
+        '+': () => '+',
+        '-': () => '-',
+      };
 
   invalidFormat() =>
       throw new FormatException("Invalid number: ${input.contents}");
@@ -729,7 +759,6 @@
  * to parse a single pattern.
  */
 class _NumberFormatParser {
-
   /**
    * The special characters in the pattern language. All others are treated
    * as literals.
@@ -1074,3 +1103,82 @@
     return input;
   }
 }
+
+/// Used primarily for currency formatting, this number-like class stores
+/// millionths of a currency unit, typically as an Int64.
+///
+/// It supports no operations other than being used for Intl number formatting.
+abstract class MicroMoney {
+  factory MicroMoney(micros) => new _MicroMoney(micros);
+}
+
+/// Used primarily for currency formatting, this stores millionths of a
+/// currency unit, typically as an Int64.
+///
+/// This private class provides the operations needed by the formatting code.
+class _MicroMoney implements MicroMoney {
+  var _micros;
+  _MicroMoney(this._micros);
+  static const _multiplier = 1000000;
+
+  get _integerPart => _micros ~/ _multiplier;
+  int get _fractionPart => (this - _integerPart)._micros.toInt().abs();
+
+  bool get isNegative => _micros.isNegative;
+
+  _MicroMoney abs() => isNegative ? new _MicroMoney(_micros.abs()) : this;
+
+  // Note that if this is done in a general way there's a risk of integer
+  // overflow on JS when multiplying out the [other] parameter, which may be
+  // an Int64. In formatting we only ever subtract out our own integer part.
+  _MicroMoney operator -(other) {
+    if (other is MicroMoney) return new _MicroMoney(_micros - other._micros);
+    return new _MicroMoney(_micros - (other * _multiplier));
+  }
+
+  _MicroMoney operator +(other) {
+    if (other is MicroMoney) return new _MicroMoney(_micros + other._micros);
+    return new _MicroMoney(_micros + (other * _multiplier));
+  }
+
+  _MicroMoney operator ~/(divisor) {
+    if (divisor is! int) {
+      throw new ArgumentError.value(divisor, 'divisor',
+          '_MicroMoney ~/ only supports int arguments.');
+    }
+    return new _MicroMoney((_integerPart ~/ divisor) * _multiplier);
+  }
+
+  _MicroMoney operator *(other) {
+    if (other is! int) {
+    throw new ArgumentError.value(other, 'other',
+        '_MicroMoney * only supports int arguments.');
+    }
+    return new _MicroMoney(
+        (_integerPart * other) * _multiplier + (_fractionPart * other));
+  }
+
+  /// Note that this only really supports remainder from an int,
+  /// not division by another MicroMoney
+  _MicroMoney remainder(other) {
+    if (other is! int) {
+      throw new ArgumentError.value(other, 'other',
+          '_MicroMoney.remainder only supports int arguments.');
+    }
+    return new _MicroMoney(_micros.remainder(other * _multiplier));
+  }
+
+  double toDouble() => _micros.toDouble() / _multiplier;
+
+  int toInt() => _integerPart.toInt();
+
+  String toString() {
+    var beforeDecimal = _integerPart.toString();
+    var decimalPart = '';
+    var fractionPart = _fractionPart;
+    if (fractionPart != 0) {
+      decimalPart = '.' + fractionPart.toString();
+    }
+    return '$beforeDecimal$decimalPart';
+  }
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index a4187d0..bbd7192 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: intl
-version: 0.12.5-dev
+version: 0.12.5
 author: Dart Team <misc@dartlang.org>
 description: Contains code to deal with internationalized/localized messages, date and number formatting and parsing, bi-directional text, and other internationalization issues.
 homepage: https://github.com/dart-lang/intl
diff --git a/test/fixnum_test.dart b/test/fixnum_test.dart
index 747a8bb..ea634c8 100644
--- a/test/fixnum_test.dart
+++ b/test/fixnum_test.dart
@@ -2,7 +2,7 @@
 // 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.
 
-library intl_test;
+library fixnum_test;
 
 import 'package:intl/intl.dart';
 import 'package:unittest/unittest.dart';
@@ -10,6 +10,7 @@
 
 var int64Values = {
   new Int64(12345): ["USD12,345.00", "1,234,500%"],
+  new Int64(-12345): ["-USD12,345.00", "-1,234,500%"],
   new Int64(0x7FFFFFFFFFFFF): [
     "USD2,251,799,813,685,247.00",
     "225,179,981,368,524,700%"
@@ -28,9 +29,36 @@
   new Int32(12345): ["USD12,345.00", "1,234,500%"],
   new Int32(0x7FFFF): ["USD524,287.00", "52,428,700%"],
   Int32.parseHex('7FFFFFF'): ["USD134,217,727.00", "13,421,772,700%"],
+  Int32.parseHex('7FFFFFFF'): ["USD2,147,483,647.00", "214,748,364,700%"],
   Int32.parseHex('80000000'): ["-USD2,147,483,648.00", "-214,748,364,800%"]
 };
 
+var microMoneyValues = {
+  new MicroMoney(new Int64(12345670000)): ["USD12,345.67", "1,234,567%"],
+  new MicroMoney(new Int64(12345671000)): ["USD12,345.67", "1,234,567%"],
+  new MicroMoney(new Int64(12345678000)): ["USD12,345.68", "1,234,568%"],
+  new MicroMoney(new Int64(-12345670000)): ["-USD12,345.67", "-1,234,567%"],
+  new MicroMoney(new Int64(-12345671000)): ["-USD12,345.67", "-1,234,567%"],
+  new MicroMoney(new Int64(-12345678000)): ["-USD12,345.68", "-1,234,568%"],
+  new MicroMoney(new Int64(12340000000)): ["USD12,340.00", "1,234,000%"],
+  new MicroMoney(new Int64(0x7FFFFFFFFFFFF)): [
+    "USD2,251,799,813.69",
+    "225,179,981,369%"
+  ],
+  new MicroMoney(Int64.parseHex('7FFFFFFFFFFFFFF')): [
+    "USD576,460,752,303.42",
+    "57,646,075,230,342%"
+  ],
+  new MicroMoney(Int64.parseHex('7FFFFFFFFFFFFFFF')): [
+    "USD9,223,372,036,854.78",
+    "922,337,203,685,478%"
+  ],
+  new MicroMoney(Int64.parseHex('8000000000000000')): [
+    "-USD9,223,372,036,854.78",
+    "-922,337,203,685,478%"
+  ]
+};
+
 main() {
   test('int64', () {
     int64Values.forEach((number, expected) {
@@ -49,4 +77,13 @@
       expect(percent, expected[1]);
     });
   });
+
+  test('micro money', () {
+    microMoneyValues.forEach((number, expected) {
+      var currency = new NumberFormat.currencyPattern().format(number);
+      expect(currency, expected.first);
+      var percent = new NumberFormat.percentPattern().format(number);
+      expect(percent, expected[1]);
+    });
+  });
 }
diff --git a/test/number_format_test.dart b/test/number_format_test.dart
index 14c8b3f..54c9114 100644
--- a/test/number_format_test.dart
+++ b/test/number_format_test.dart
@@ -19,6 +19,7 @@
   "-1": -1,
   "-2": -2.0,
   "-0.01": -0.01,
+  "-1.23": -1.23,
   "0.001": 0.001,
   "0.01": 0.01,
   "0.1": 0.1,
@@ -38,7 +39,13 @@
 };
 
 /** Test numbers that we can't parse because we lose precision in formatting.*/
-var testNumbersWeCannotReadBack = {"3.142": PI,};
+var testNumbersWeCannotReadBack = {
+  "3.142": PI,
+  "-1.234": -1.2342,
+  "-1.235": -1.2348,
+  "1.234": 1.2342,
+  "1.235": 1.2348
+};
 
 /** Test numbers that won't work in Javascript because they're too big. */
 var testNumbersOnlyForTheVM = {