Dart intl: fix compact number formatting.
To be consistent with other intl, do not try to outsmart exponent-based templates in CLDR.

PiperOrigin-RevId: 426127334
diff --git a/lib/src/intl/compact_number_format.dart b/lib/src/intl/compact_number_format.dart
index 32c94ab..0d4d52d 100644
--- a/lib/src/intl/compact_number_format.dart
+++ b/lib/src/intl/compact_number_format.dart
@@ -13,18 +13,6 @@
   /// negative.
   _CompactStyle styleForSign(number);
 
-  /// How many total digits do we expect in the number.
-  ///
-  /// If the pattern is
-  ///
-  ///       4: '00K',
-  ///
-  /// then this is 5, meaning we expect this to be a 5-digit (or more)
-  /// number. We will scale by 1000 and expect 2 integer digits remaining, so we
-  /// get something like '12K'. This is used to find the closest pattern for a
-  /// number.
-  int get totalDigits;
-
   /// What should we divide the number by in order to print. Normally it is
   /// either `10^normalizedExponent` or 1 if we shouldn't divide at all.
   int get divisor;
@@ -43,7 +31,6 @@
   final _CompactStyle negativeStyle;
   _CompactStyle styleForSign(number) =>
       number < 0 ? negativeStyle : positiveStyle;
-  int get totalDigits => positiveStyle.totalDigits;
   int get divisor => positiveStyle.divisor;
   List<_CompactStyle> get allStyles => [positiveStyle, negativeStyle];
 }
@@ -57,59 +44,27 @@
 ///       4: '00K'
 /// which matches
 ///
-///       _CompactStyle(pattern: '00K', normalizedExponent: 4, divisor: 1000,
-///           expectedDigits: 1, prefix: '', suffix: 'K');
-///
-/// where expectedDigits is the number of zeros.
+///       _CompactStyle(pattern: '00K', divisor: 1000,
+///           prefix: '', suffix: 'K');
 class _CompactStyle extends _CompactStyleBase {
   _CompactStyle(
-      {this.pattern,
-      this.normalizedExponent = 0,
-      this.divisor = 1,
-      this.expectedDigits = 1,
-      this.prefix = '',
-      this.suffix = ''});
+      {this.pattern, this.divisor = 1, this.prefix = '', this.suffix = ''});
 
   /// The pattern on which this is based.
   ///
   /// We don't actually need this, but it makes debugging easier.
   String? pattern;
 
-  /// The normalized scientific notation exponent for which the format applies.
-  ///
-  /// So if this is 3, we expect it to apply for numbers from 1000 and up.
-  /// Typically it would be from 1000 to 9999, but that depends if there's a
-  /// style for 4 or not. This is the CLDR index of the pattern, and usually
-  /// determines the divisor, but if the pattern is just a 0 with no prefix or
-  /// suffix then we don't divide at all.
-  int normalizedExponent;
-
   /// What should we divide the number by in order to print. Normally is either
   /// 10^normalizedExponent or 1 if we shouldn't divide at all.
   int divisor;
 
-  /// How many integer digits do we expect to print - the number of zeros in the
-  /// CLDR pattern.
-  int expectedDigits;
-
   /// Text we put in front of the number part.
   String prefix;
 
   /// Text we put after the number part.
   String suffix;
 
-  /// How many total digits do we expect in the number.
-  ///
-  /// If the pattern is
-  ///
-  ///       4: '00K',
-  ///
-  /// then this is 5, meaning we expect this to be a 5-digit (or more)
-  /// number. We will scale by 1000 and expect 2 integer digits remaining, so we
-  /// get something like '12K'. This is used to find the closest pattern for a
-  /// number.
-  int get totalDigits => normalizedExponent + expectedDigits - 1;
-
   /// Return true if this is the fallback compact pattern, printing the number
   /// un-compacted. e.g. 1200 might print as '1.2K', but 12 just prints as '12'.
   ///
@@ -118,15 +73,6 @@
   /// for a particular currency (e.g. two for USD, zero for JPY)
   bool get isFallback => pattern == null || pattern == '0';
 
-  /// Should we print the number as-is, without dividing.
-  ///
-  /// This happens if the pattern has no abbreviation for scaling (e.g. K, M).
-  /// So either the pattern is empty or it is of a form like '0 $'. This is a
-  /// workaround for locales like 'it', which include patterns with no suffix
-  /// for numbers >= 1000 but < 1,000,000.
-  bool get printsAsIs =>
-      isFallback || pattern!.replaceAll(RegExp('[0\u00a0\u00a4]'), '').isEmpty;
-
   _CompactStyle styleForSign(number) => this;
   List<_CompactStyle> get allStyles => [this];
 
@@ -155,12 +101,7 @@
       divisor = pow(10, normalizedExponent - integerDigits + 1) as int;
     }
     return _CompactStyle(
-        pattern: pattern,
-        normalizedExponent: normalizedExponent,
-        expectedDigits: integerDigits,
-        prefix: prefix,
-        suffix: suffix,
-        divisor: divisor);
+        pattern: pattern, prefix: prefix, suffix: suffix, divisor: divisor);
   }
 }
 
@@ -175,7 +116,8 @@
   /// A default, using the decimal pattern, for the `getPattern` constructor parameter.
   static String _forDecimal(NumberSymbols symbols) => symbols.DECIMAL_PATTERN;
 
-  final List<_CompactStyleBase> _styles;
+  // Map exponent => style.
+  final Map<int, _CompactStyleBase> _styles;
 
   factory _CompactNumberFormat(
       {String? locale,
@@ -213,7 +155,7 @@
 
     var compactSymbols = compactNumberSymbols[locale]!;
 
-    var styles = <_CompactStyleBase>[];
+    var styles = <int, _CompactStyleBase>{};
     switch (formatType) {
       case _CompactFormatType.COMPACT_DECIMAL_SHORT_PATTERN:
         patterns = compactSymbols.COMPACT_DECIMAL_SHORT_PATTERN;
@@ -233,19 +175,14 @@
     patterns.forEach((int exponent, String pattern) {
       if (pattern.contains(';')) {
         var patterns = pattern.split(';');
-        styles.add(_CompactStyleWithNegative(
+        styles[exponent] = _CompactStyleWithNegative(
             _CompactStyle.createStyle(patterns.first, exponent),
-            _CompactStyle.createStyle(patterns.last, exponent)));
+            _CompactStyle.createStyle(patterns.last, exponent));
       } else {
-        styles.add(_CompactStyle.createStyle(pattern, exponent));
+        styles[exponent] = _CompactStyle.createStyle(pattern, exponent);
       }
     });
 
-    // Reverse the styles so that we look through them from largest to smallest.
-    styles = styles.reversed.toList();
-    // Add a fallback style that just prints the number.
-    styles.add(_CompactStyle());
-
     return _CompactNumberFormat._(
         name,
         currencySymbol,
@@ -286,7 +223,7 @@
   String format(number) {
     var style = _styleFor(number);
     _style = style;
-    final divisor = style.printsAsIs ? 1 : style.divisor;
+    final divisor = style.isFallback ? 1 : style.divisor;
     final numberToFormat = _divide(number, divisor);
     var formatted = super.format(numberToFormat);
     var prefix = style.prefix;
@@ -373,20 +310,23 @@
       var rounded = (number.toDouble() / divisor).round() * divisor;
       digitLength = NumberFormat.numberOfIntegerDigits(rounded);
     }
-    for (var style in _styles) {
-      if (digitLength > style.totalDigits) {
-        return style.styleForSign(number);
+
+    var expectedExponent = digitLength - 1;
+    _CompactStyleBase? style;
+    for (var entries in _styles.entries) {
+      if (entries.key > expectedExponent) {
+        break;
       }
+      style = entries.value;
     }
-    throw FormatException(
-        'No compact style found for number. This should not happen', number);
+    return style?.styleForSign(number) ?? _defaultCompactStyle;
   }
 
   Iterable<_CompactStyle> get _stylesForSearching =>
-      _styles.reversed.expand((x) => x.allStyles);
+      _styles.values.expand((x) => x.allStyles);
 
   num parse(String text) {
-    for (var style in _stylesForSearching) {
+    for (var style in [_defaultCompactStyle, ..._stylesForSearching]) {
       if (text.startsWith(style.prefix) && text.endsWith(style.suffix)) {
         var numberText = text.substring(
             style.prefix.length, text.length - style.suffix.length);
@@ -409,3 +349,5 @@
     }
   }
 }
+
+final _defaultCompactStyle = _CompactStyle();
diff --git a/test/number_format_compact_test.dart b/test/number_format_compact_test.dart
index 4b82be5..c08b31d 100644
--- a/test/number_format_compact_test.dart
+++ b/test/number_format_compact_test.dart
@@ -168,24 +168,8 @@
 
 // TODO(alanknight): Don't just skip the whole locale if there's one problem
 // case.
-// TODO(alanknight): Fix the problems, or at least figure out precisely where
-// the differences are.
-var _skipLocalsShort = {
-  'bn', // Bad prefix.
-  'ca', // For CA, CLDR rules are different. Jumps from 0000 to 00 prefix, so
-  // 11 digits prints as 11900.
-  'es_419', // Some odd formatting rules for these which seem to be different
-  // from CLDR. wants e.g. '160000000000k' Actual: '160 B'
-  'es_ES', // The reverse of es_419 for a few cases. We're printing a longer
-  // form.
-  'es_US', // Like es_419 but not as many of them. e.g. Expected: '87700k'
-  // Actual: '87.7 M'
-  'es_MX', // like es_419
-  'es',
-  'ka', // K Slight difference in the suffix
-  'kk', 'mn', // We're picking the wrong pattern for 654321.
-  'lo', 'mk', 'my',
-  'ur', // UR Fails one with Expected: '15 ٹریلین'  Actual: '150 کھرب'
+var _skipLocalsShort = <String>{
+  // None ;o)
 };
 
 /// Locales that have problems in the long format.
@@ -197,7 +181,7 @@
 ///
 //TODO(alanknight): Narrow these down to particular numbers. Often it's just
 // 999999.
-var _skipLocalesLong = {
+var _skipLocalesLong = <String>{
   'ar', 'ar_DZ', 'ar_EG',
   'be', 'bg', 'bn', 'bs',
   'ca', 'cs', 'da', 'de', 'de_AT', 'de_CH', 'el', 'es', 'es_419', 'es_ES',
@@ -208,10 +192,9 @@
   'ga', 'gl',
   'gsw', // GSW seems like we have long forms and pyICU doesn't
   'hr', 'is', 'it', 'it_CH',
-  'km',
   'lt', 'lv', 'mk',
   'my', // Seems to come out in the reverse order
-  'nb', 'ne', 'no', 'no_NO', 'pl',
+  'nb', 'no', 'no_NO', 'pl',
   'pt', // PT has some issues with scale as well, but I think it's differences
   // in the patterns.
   'pt_BR', 'pt_PT', 'ro', 'ru',
@@ -265,7 +248,8 @@
   }
   var parsed = format.parse(formatted);
   var rounded = _roundForPrinting(number, format);
-  expect((parsed - rounded) / rounded < 0.001, isTrue);
+  expect((parsed - rounded) / rounded < 0.001, isTrue,
+      reason: 'Parsed: $parsed, rounded: $rounded');
 }
 
 /// Duplicate a bit of the logic in formatting, where if we have a