From e4e1276fe3045480a13e063c80b645546612b90a Mon Sep 17 00:00:00 2001 From: Katie Sievert Date: Tue, 25 Apr 2017 11:49:44 -0700 Subject: [PATCH 1/2] Number: Add support for compact option (short/long) (1/2) --- README.md | 3 + doc/api/currency/currency-formatter.md | 8 ++ doc/api/number/number-formatter.md | 28 +++++ src/number.js | 13 ++- src/number/compact.js | 22 ++++ src/number/format-properties.js | 10 +- src/number/format.js | 60 +++++++++-- src/number/formatter-fn.js | 4 +- src/number/pattern-re.js | 4 +- test/unit/number/format.js | 135 ++++++++++++++++++++++++- 10 files changed, 266 insertions(+), 21 deletions(-) create mode 100644 src/number/compact.js diff --git a/README.md b/README.md index 0a15bf03d..932b87f80 100644 --- a/README.md +++ b/README.md @@ -460,6 +460,9 @@ Return a function that formats a number according to the given options or locale .numberFormatter({ style: "percent" })( 0.5 ) // > "50%" + +.numberFormatter({ compact: "short", maximumFractionDigits: 0 })( 14305 ) +// > "14K" ``` [Read more...](doc/api/number/number-formatter.md) diff --git a/doc/api/currency/currency-formatter.md b/doc/api/currency/currency-formatter.md index 6290c3f3a..c649f0973 100644 --- a/doc/api/currency/currency-formatter.md +++ b/doc/api/currency/currency-formatter.md @@ -129,6 +129,14 @@ formatter = Globalize.currencyFormatter( "USD", { formatter( 1.491 ); // > "$1.50" + +formatter = Globalize.currencyFormatter( "USD", { + maximumFractionDigits: 0, + compact: "short" +}); + +formatter( 12830000000 ); +// > "$13B" ``` For improved performance on iterations, first create the formatter. Then, reuse it on each loop. diff --git a/doc/api/number/number-formatter.md b/doc/api/number/number-formatter.md index ac8b1659e..d8e01d807 100644 --- a/doc/api/number/number-formatter.md +++ b/doc/api/number/number-formatter.md @@ -30,6 +30,10 @@ Optional. String with rounding method `ceil`, `floor`, `round` (default), or `tr Optional. Boolean (default is true) value indicating whether a grouping separator should be used. +#### options.compact + +Optional. String `short` or `long` indicating which compact number format should be used to represent the number. + ### Examples #### Static Formatter @@ -139,6 +143,30 @@ frFormatter( 0.0005 ); // > "0,05 %" ``` +#### Formatting Compact Numbers + +Long numbers can be represented in a compact format, with `short` using abbreviated units and `long` using the full unit name. + +```javascript +var shortFormatter = Globalize( "en" ).numberFormatter({ + compact: "short", + maximumFractionDigits: 0, + style: "decimal" +}); + +var longFormatter = Globalize( "en" ).numberFormatter({ + compact: "long", + maximumFractionDigits: 0, + style: "decimal" +}); + +shortFormatter( 27588910 ); +// > "28M" + +longFormatter( 27588910 ); +// > "28 million" +``` + #### Configuring Rounding Numbers with a decreased amount of decimal places can be rounded up, rounded down, rounded arithmetically, or truncated by setting the `round` option to `ceil`, `floor`, `round` (default), or `truncate`. diff --git a/src/number.js b/src/number.js index 4922e4b27..08164cae0 100644 --- a/src/number.js +++ b/src/number.js @@ -68,7 +68,7 @@ function validateDigits( properties ) { */ Globalize.numberFormatter = Globalize.prototype.numberFormatter = function( options ) { - var args, cldr, pattern, properties, returnFn; + var args, cldr, pattern, pluralGenerator, properties, returnFn; validateParameterTypePlainObject( options, "options" ); @@ -93,9 +93,14 @@ Globalize.prototype.numberFormatter = function( options ) { validateDigits( properties ); - returnFn = numberFormatterFn( properties ); - - runtimeBind( args, cldr, returnFn, [ properties ] ); + if ( options.compact ) { + pluralGenerator = this.pluralGenerator(); + returnFn = numberFormatterFn( properties, pluralGenerator ); + runtimeBind( args, cldr, returnFn, [ properties, pluralGenerator ] ); + } else { + returnFn = numberFormatterFn( properties ); + runtimeBind( args, cldr, returnFn, [ properties ] ); + } return returnFn; }; diff --git a/src/number/compact.js b/src/number/compact.js new file mode 100644 index 000000000..52ae93a84 --- /dev/null +++ b/src/number/compact.js @@ -0,0 +1,22 @@ +define([ + "./numbering-system" +], function( numberNumberingSystem ) { + +/** + * Compact( name, cldr ) + * + * @compactType [String] Compact mode, `short` or `long`. + * + * @cldr [Cldr instance]. + * + * Return the localized compact map for the given compact mode. + */ +return function( compactType, cldr ) { + return cldr.main([ + "numbers/decimalFormats-numberSystem-" + numberNumberingSystem( cldr ), + compactType, + "decimalFormat" + ]); +}; + +}); diff --git a/src/number/format-properties.js b/src/number/format-properties.js index f9636690a..79a602499 100644 --- a/src/number/format-properties.js +++ b/src/number/format-properties.js @@ -1,12 +1,13 @@ define([ + "./compact", "./numbering-system-digits-map", "./pattern-properties", "./symbol", "./symbol/map", "./symbol/name", "../util/number/round" -], function( numberNumberingSystemDigitsMap, numberPatternProperties, numberSymbol, numberSymbolMap, - numberSymbolName, numberRound ) { +], function( numberCompact, numberNumberingSystemDigitsMap, numberPatternProperties, numberSymbol, + numberSymbolMap, numberSymbolName, numberRound ) { /** * formatProperties( pattern, cldr [, options] ) @@ -63,6 +64,10 @@ return function( pattern, cldr, options ) { numberNumberingSystemDigitsMap( cldr ) ]); + if ( options.compact ) { + properties[20] = numberCompact( options.compact, cldr ); + } + getOptions( "minimumIntegerDigits", 2 ); getOptions( "minimumFractionDigits", 3 ); getOptions( "maximumFractionDigits", 4 ); @@ -98,6 +103,7 @@ return function( pattern, cldr, options ) { // 17: @nanSymbol [String] NaN symbol. // 18: @symbolMap [Object] A bunch of other symbols. // 19: @nuDigitsMap [Array] Digits map if numbering system is different than `latn`. + // 20: @compactMap [Object] Map of per-digit-count format patterns for specified compact mode. return properties; }; diff --git a/src/number/format.js b/src/number/format.js index a77cfc846..2fc06bc6f 100644 --- a/src/number/format.js +++ b/src/number/format.js @@ -2,9 +2,10 @@ define([ "./format/grouping-separator", "./format/integer-fraction-digits", "./format/significant-digits", + "./pattern-re", "../util/remove-literal-quotes" ], function( numberFormatGroupingSeparator, numberFormatIntegerFractionDigits, - numberFormatSignificantDigits, removeLiteralQuotes ) { + numberFormatSignificantDigits, numberPatternRe, removeLiteralQuotes ) { /** * format( number, properties ) @@ -16,11 +17,11 @@ define([ * Return the formatted number. * ref: http://www.unicode.org/reports/tr35/tr35-numbers.html */ -return function( number, properties ) { - var infinitySymbol, maximumFractionDigits, maximumSignificantDigits, minimumFractionDigits, - minimumIntegerDigits, minimumSignificantDigits, nanSymbol, nuDigitsMap, padding, prefix, - primaryGroupingSize, pattern, ret, round, roundIncrement, secondaryGroupingSize, suffix, - symbolMap; +return function( number, properties, pluralGenerator ) { + var compactMap, infinitySymbol, maximumFractionDigits, maximumSignificantDigits, + minimumFractionDigits, minimumIntegerDigits, minimumSignificantDigits, nanSymbol, nuDigitsMap, + padding, prefix, primaryGroupingSize, pattern, ret, round, roundIncrement, + secondaryGroupingSize, suffix, symbolMap; padding = properties[ 1 ]; minimumIntegerDigits = properties[ 2 ]; @@ -36,6 +37,7 @@ return function( number, properties ) { nanSymbol = properties[ 17 ]; symbolMap = properties[ 18 ]; nuDigitsMap = properties[ 19 ]; + compactMap = properties[ 20 ]; // NaN if ( isNaN( number ) ) { @@ -57,8 +59,6 @@ return function( number, properties ) { return prefix + infinitySymbol + suffix; } - ret = prefix; - // Percent if ( pattern.indexOf( "%" ) !== -1 ) { number *= 100; @@ -68,6 +68,32 @@ return function( number, properties ) { number *= 1000; } + var compactPattern, compactDigits, compactProperties, divisor, pluralForm, zeroes, + originalNumber; + + // Compact mode: initial number digit processing + if ( compactMap ) { + originalNumber = number; + zeroes = Array( Math.floor( number ).toString().length ).join( "0" ); + if ( zeroes.length >= 3 ) { + + // use default plural form to perform initial decimal shift + compactPattern = compactMap[ "1" + zeroes + "-count-other" ]; + + if ( compactPattern ) { + compactDigits = compactPattern.split( "0" ).length - 1; + divisor = zeroes.length - ( compactDigits - 1 ); + number = number / Math.pow( 10, divisor ); + + // Some languages specify no pattern for certain digit lengths, represented as "0". + // If no pattern, original number should remain uncompacted. + if ( compactPattern === "0" ) { + number = originalNumber; + } + } + } + } + // Significant digit format if ( !isNaN( minimumSignificantDigits * maximumSignificantDigits ) ) { number = numberFormatSignificantDigits( number, minimumSignificantDigits, @@ -79,6 +105,22 @@ return function( number, properties ) { minimumFractionDigits, maximumFractionDigits, round, roundIncrement ); } + // Compact mode: apply formatting + if ( compactMap && compactPattern ) { + pluralForm = pluralGenerator ? pluralGenerator( number ) : "other"; + compactPattern = compactMap[ "1" + zeroes + "-count-" + pluralForm ] || compactPattern; + + // Some languages specify no pattern for certain digit lengths, represented as "0". + // Only apply compact pattern if one is specified. + if ( compactPattern !== "0" ) { + compactProperties = compactPattern.match( numberPatternRe ); + + // update prefix/suffix with compact prefix/suffix + prefix += compactProperties[ 1 ]; + suffix = compactProperties[ 11 ] + suffix; + } + } + // Remove the possible number minus sign number = number.replace( /^-/, "" ); @@ -88,6 +130,8 @@ return function( number, properties ) { secondaryGroupingSize ); } + ret = prefix; + ret += number; // Scientific notation diff --git a/src/number/formatter-fn.js b/src/number/formatter-fn.js index f4a61bef1..c03539ef0 100644 --- a/src/number/formatter-fn.js +++ b/src/number/formatter-fn.js @@ -4,12 +4,12 @@ define([ "./format" ], function( validateParameterPresence, validateParameterTypeNumber, numberFormat ) { -return function( properties ) { +return function( properties, pluralGenerator ) { return function numberFormatter( value ) { validateParameterPresence( value, "value" ); validateParameterTypeNumber( value, "value" ); - return numberFormat( value, properties ); + return numberFormat( value, properties, pluralGenerator ); }; }; diff --git a/src/number/pattern-re.js b/src/number/pattern-re.js index 2d2b206b1..bbb0b0893 100644 --- a/src/number/pattern-re.js +++ b/src/number/pattern-re.js @@ -26,7 +26,7 @@ define(function() { * * suffix = non_number_stuff * - * non_number_stuff = regexp(('[^']+'|''|[^*#@0,.E])*) + * non_number_stuff = regexp(.*?) * * * Regexp groups: @@ -45,6 +45,6 @@ define(function() { * 11: suffix * 12: - */ -return ( /^(('([^']|'')*'|[^*#@0,.E])*)(\*.)?((([#,]*[0,]*0+)(\.0*[0-9]*#*)?)|([#,]*@+#*))(E\+?0+)?(('[^']+'|''|[^*#@0,.E])*)$/ ); +return ( /^(('([^']|'')*'|[^*#@0,.E])*)(\*.)?((([#,]*[0,]*0+)(\.0*[0-9]*#*)?)|([#,]*@+#*))(E\+?0+)?(.*?)$/ ); }); diff --git a/test/unit/number/format.js b/test/unit/number/format.js index 10f5f2a5d..b47d7217b 100644 --- a/test/unit/number/format.js +++ b/test/unit/number/format.js @@ -6,18 +6,19 @@ define([ "json!cldr-data/main/en/numbers.json", "json!cldr-data/main/es/numbers.json", "json!cldr-data/main/fa/numbers.json", + "json!cldr-data/main/hu/numbers.json", "json!cldr-data/main/zh/numbers.json", "json!cldr-data/supplemental/likelySubtags.json", "json!cldr-data/supplemental/numberingSystems.json", "cldr/event", "cldr/supplemental" -], function( Cldr, format, properties, arNumbers, enNumbers, esNumbers, faNumbers, zhNumbers, - likelySubtags, numberingSystems ) { +], function( Cldr, format, properties, arNumbers, enNumbers, esNumbers, faNumbers, huNumbers, + zhNumbers, likelySubtags, numberingSystems ) { // 1: Earth average diameter according to: // http://www.wolframalpha.com/input/?i=earth+diameter -var ar, en, es, fa, zh, +var ar, en, es, fa, hu, zh, zhSimplified, deci = 0.1, earthDiameter = 12735, /* 1 */ pi = 3.14159265359; @@ -27,6 +28,7 @@ Cldr.load( enNumbers, esNumbers, faNumbers, + huNumbers, zhNumbers, likelySubtags, numberingSystems @@ -36,10 +38,20 @@ ar = new Cldr( "ar" ); en = new Cldr( "en" ); es = new Cldr( "es" ); fa = new Cldr( "fa" ); +hu = new Cldr( "hu" ); zh = new Cldr( "zh-u-nu-native" ); +zhSimplified = new Cldr( "zh" ); QUnit.module( "Number Format" ); +function oneOrOtherPluralGenerator( plural ) { + if ( plural === "1" ) { + return "one"; + } else { + return "other"; + } +} + /** * Integers */ @@ -69,6 +81,123 @@ QUnit.test( "should format negative integer", function( assert ) { assert.equal( format( -earthDiameter, properties( "0;(0.0##)", en ) ), "(12735)" ); }); +/** + * Compact Numbers + */ + +QUnit.test( "integers should format in compact mode", function( assert ) { + assert.equal( format( 273.7, properties( "#0", en, { compact: "short" } ) ), "274" ); + assert.equal( format( 273.7, properties( "#0", en, { compact: "long" } ) ), "274" ); + assert.equal( format( 273, properties( "#0", en, { compact: "short" } ) ), "273" ); + assert.equal( format( 273, properties( "#0", en, { compact: "long" } ) ), "273" ); + assert.equal( format( 573, properties( "#0", en, { compact: "short" } ) ), "573" ); + assert.equal( format( 573, properties( "#0", en, { compact: "long" } ) ), "573" ); + assert.equal( format( 1273, properties( "#0", en, { compact: "short" } ) ), "1K" ); + assert.equal( format( 1273, properties( "#0", en, { compact: "long" } ) ), "1 thousand" ); + assert.equal( format( 1273000, properties( "#0", es, { + compact: "long" + } ), oneOrOtherPluralGenerator ), "1 millón" ); + assert.equal( format( 2273000, properties( "#0", es, { + compact: "long" + } ), oneOrOtherPluralGenerator ), "2 millones" ); + assert.equal( format( 9999.9, properties( "#0", en, { compact: "long" } ) ), "10 thousand" ); + assert.equal( format( 12735, properties( "#0", en, { compact: "short" } ) ), "13K" ); + assert.equal( format( 12735, properties( "#0", en, { compact: "long" } ) ), "13 thousand" ); + assert.equal( format( 127350, properties( "#0", en, { compact: "short" } ) ), "127K" ); + assert.equal( format( 127350, properties( "#0", en, { compact: "long" } ) ), "127 thousand" ); + assert.equal( format( 1273500, properties( "#0", en, { compact: "short" } ) ), "1M" ); + assert.equal( format( 1273500, properties( "#0", en, { compact: "long" } ) ), "1 million" ); + assert.equal( format( -1273500, properties( "#0", en, { compact: "short" } ) ), "-1M" ); + assert.equal( format( -1273500, properties( "#0", en, { compact: "long" } ) ), "-1 million" ); + assert.equal( format( -1273500, properties( "#0;(#0)", en, { compact: "short" } ) ), "(1M)" ); + assert.equal( format( -1273500, properties( "#0;(#0)", en, { compact: "long" } ) ), "(1 million)" ); + + // some hungarian short formats have a terminating E, which is treated as a special + // character in non-compact formats + // \u00A0 is a unicode non-breaking space + assert.equal( format( 1273, properties( "#0", hu, { compact: "short" } ) ), "1\u00A0E" ); + assert.equal( format( 1273, properties( "#0", hu, { compact: "long" } ) ), "1 ezer" ); + assert.equal( format(9000000, properties( "#0", hu, { compact: "short" } ) ), "9\u00A0M" ); + assert.equal( format(9000000, properties( "#0", hu, { compact: "long" } ) ), "9 millió" ); +}); + +QUnit.test( "unsupported numbers should apply no compacting in compact mode", function( assert ) { + assert.equal( format( 0.01, properties( "#0.##", en, { compact: "short" } ) ), "0.01" ) + assert.equal( format( 1234000000000000, properties( "#0", en, { compact: "short" } ) ), "1234000000000000" ) +}); + +QUnit.test( "decimals should format in compact mode", function( assert ) { + assert.equal( format( 273.7, properties( "#0.#", en, { compact: "short" } ) ), "273.7" ); + assert.equal( format( 273.7, properties( "#0.#", en, { compact: "long" } ) ), "273.7" ); + assert.equal( format( 273, properties( "#0.#", en, { compact: "short" } ) ), "273" ); + assert.equal( format( 273, properties( "#0.#", en, { compact: "long" } ) ), "273" ); + assert.equal( format( 573, properties( "#0.#", en, { compact: "short" } ) ), "573" ); + assert.equal( format( 573, properties( "#0.#", en, { compact: "long" } ) ), "573" ); + assert.equal( format( 1273, properties( "#0.#", en, { compact: "short" } ) ), "1.3K" ); + assert.equal( format( 1273, properties( "#0.#", en, { compact: "long" } ) ), "1.3 thousand" ); + assert.equal( format( 1273000, properties( "#0.#", es, { + compact: "long" + } ), oneOrOtherPluralGenerator ), "1,3 millones" ); + assert.equal( format( 2273000, properties( "#0.#", es, { + compact: "long" + } ), oneOrOtherPluralGenerator ), "2,3 millones" ); + assert.equal( format( 9999.9, properties( "#0.#", en, { compact: "long" } ) ), "10 thousand" ); + assert.equal( format( 12735, properties( "#0.#", en, { compact: "short" } ) ), "12.7K" ); + assert.equal( format( 12735, properties( "#0.#", en, { compact: "long" } ) ), "12.7 thousand" ); + assert.equal( format( 127350, properties( "#0.#", en, { compact: "short" } ) ), "127.4K" ); + assert.equal( format( 127350, properties( "#0.#", en, { compact: "long" } ) ), "127.4 thousand" ); + assert.equal( format( 1273500, properties( "#0.#", en, { compact: "short" } ) ), "1.3M" ); + assert.equal( format( 1273500, properties( "#0.#", en, { compact: "long" } ) ), "1.3 million" ); + assert.equal( format( -1273500, properties( "#0.#", en, { compact: "short" } ) ), "-1.3M" ); + assert.equal( format( -1273500, properties( "#0.#", en, { compact: "long" } ) ), "-1.3 million" ); + assert.equal( format( -1273500, properties( "#0.#;(#0.#)", en, { compact: "short" } ) ), "(1.3M)" ); + assert.equal( format( -1273500, properties( "#0.#;(#0.#)", en, { compact: "long" } ) ), "(1.3 million)" ); +}); + +QUnit.test( "decimals should support rounding in compact mode", function( assert ) { + assert.equal( format( 12735, properties( "#0.##", en, { + compact: "short", + maximumFractionDigits: 2, + minimumFractionDigits: 1 + } ) ), "12.74K" ); + assert.equal( format( 12735, properties( "#0.##", en, { + compact: "short", + maximumFractionDigits: 2, + minimumFractionDigits: 1, + round: "ceil" + } ) ), "12.74K" ); + assert.equal( format( 12735, properties( "#0.##", en, { + compact: "short", + maximumFractionDigits: 2, + minimumFractionDigits: 1, + round: "floor" + } ) ), "12.73K" ); + assert.equal( format( 12735, properties( "#0.##", en, { + compact: "short", + maximumFractionDigits: 2, + minimumFractionDigits: 1, + round: "truncate" + } ) ), "12.73K" ); +}); + +QUnit.test( "integers should support significant digits in compact mode", function( assert ) { + assert.equal( format( 12735, properties( "@@@", en, { compact: "short" } ) ), "12.7K" ); + assert.equal( format( 12735, properties( "@@", en, { compact: "short" } ) ), "13K" ); + assert.equal( format( 12735, properties( "@", en, { compact: "short" } ) ), "10K" ); +}); + +QUnit.test( "integers should handle default (no) pattern in compact mode", function( assert ) { + assert.equal( format( 2737, properties( "#0.#", zhSimplified, { compact: "short" } ) ), "2737" ); + assert.equal( format( 2737, properties( "#0.#", zhSimplified, { compact: "long" } ) ), "2737" ); + assert.equal( format( 27371, properties( "#0.#", zhSimplified, { compact: "short" } ) ), "2.7万" ); + assert.equal( format( 27371, properties( "#0.#", zhSimplified, { compact: "long" } ) ), "2.7万" ); +}); + +QUnit.test( "percents should format in compact mode", function( assert ) { + assert.equal( format( 127, properties( "#0%", en, { compact: "short" } ) ), "13K%" ); + assert.equal( format( 127, properties( "#0%", en, { compact: "long" } ) ), "13 thousand%" ); +}); + /** * Decimals */ From a975fd72430c9589701428b6b8be4b85d7e1488f Mon Sep 17 00:00:00 2001 From: Rafael Xavier de Souza Date: Fri, 21 Jul 2017 17:04:17 -0300 Subject: [PATCH 2/2] Number: Add support for compact option (short/long) (2/2) --- doc/api/currency/currency-formatter.md | 37 ++- doc/api/number/number-formatter.md | 19 +- src/number/compact-pattern-re.js | 21 ++ src/number/compact.js | 27 ++- src/number/format-properties.js | 12 + src/number/format.js | 59 +++-- src/number/pattern-re.js | 4 +- test/unit/number/format.js | 323 +++++++++++++++---------- 8 files changed, 330 insertions(+), 172 deletions(-) create mode 100644 src/number/compact-pattern-re.js diff --git a/doc/api/currency/currency-formatter.md b/doc/api/currency/currency-formatter.md index c649f0973..c65093c71 100644 --- a/doc/api/currency/currency-formatter.md +++ b/doc/api/currency/currency-formatter.md @@ -20,6 +20,8 @@ Number to be formatted, eg. `9.99`. ### Example +#### Static Formatter + Prior to using any currency methods, you must load `cldr/main/{locale}/currencies.json`, `cldr/supplemental/currencyData.json`, and the CLDR content required by the number module. If using plural messages, you also must load the CLDR content required by the plural module. Read [CLDR content][] if you need more information. [CLDR content]: ../../../README.md#2-cldr-content @@ -37,6 +39,8 @@ formatter( 9.99 ); ``` +#### Instance Formatter + You can use the instance method `.currencyFormatter()`, which uses the instance locale. ```javascript @@ -62,6 +66,8 @@ For comparison, follow the formatting output of different symbols in different l | `.currencyFormatter( "GBP" )( 1 )` | `£1.00` | `1,00 £` | `£ 1.00` | | `.currencyFormatter( "BRL" )( 1 )` | `R$1.00` | `1,00 R$` | `R$ 1.00` | +#### Configuring style + For the accounting variation of the symbol format, use `style: "accounting"`. ```javascript @@ -109,6 +115,8 @@ formatter( 9.99 ); // > "9.99 USD" ``` +#### Configuring inherited number options + Override the number of digits, grouping separators, rounding function or any other [`.numberFormatter()` options](../number/number-formatter.md). ```javascript @@ -129,16 +137,39 @@ formatter = Globalize.currencyFormatter( "USD", { formatter( 1.491 ); // > "$1.50" +``` -formatter = Globalize.currencyFormatter( "USD", { - maximumFractionDigits: 0, +#### Formatting Compact Currencies + +```js +var shortFormatter = Globalize( "en" ).currencyFormatter( "USD", { compact: "short" }); -formatter( 12830000000 ); +var longFormatter = Globalize( "en" ).currencyFormatter( "USD", { + compact: "long" +}); + +shortFormatter( 12830000000 ); // > "$13B" + +longFormatter( 12830000000 ); +// > "$13 billion" ``` +The minimumSignificantDigits and maximumSignificantDigits options are specially useful to control the number of digits to display. + +```js +Globalize( "en" ).formatCurrency( 12830000000, "USD", { + compact: "short", + minimumSignificantDigits: 3, + maximumSignificantDigits: 3 +}); +// > "$12.8B" +``` + +#### Performance Suggestion + For improved performance on iterations, first create the formatter. Then, reuse it on each loop. ```javascript diff --git a/doc/api/number/number-formatter.md b/doc/api/number/number-formatter.md index d8e01d807..dc51802d2 100644 --- a/doc/api/number/number-formatter.md +++ b/doc/api/number/number-formatter.md @@ -149,15 +149,11 @@ Long numbers can be represented in a compact format, with `short` using abbrevia ```javascript var shortFormatter = Globalize( "en" ).numberFormatter({ - compact: "short", - maximumFractionDigits: 0, - style: "decimal" + compact: "short" }); var longFormatter = Globalize( "en" ).numberFormatter({ - compact: "long", - maximumFractionDigits: 0, - style: "decimal" + compact: "long" }); shortFormatter( 27588910 ); @@ -167,6 +163,17 @@ longFormatter( 27588910 ); // > "28 million" ``` +The minimumSignificantDigits and maximumSignificantDigits options are specially useful to control the number of digits to display. + +```js +Globalize( "en" ).formatNumber( 27588910, { + compact: "short", + minimumSignificantDigits: 3, + maximumSignificantDigits: 3 +}); +// > "27.6M" +``` + #### Configuring Rounding Numbers with a decreased amount of decimal places can be rounded up, rounded down, rounded arithmetically, or truncated by setting the `round` option to `ceil`, `floor`, `round` (default), or `truncate`. diff --git a/src/number/compact-pattern-re.js b/src/number/compact-pattern-re.js new file mode 100644 index 000000000..dc4840ad7 --- /dev/null +++ b/src/number/compact-pattern-re.js @@ -0,0 +1,21 @@ +define(function() { + +/** + * EBNF representation: + * + * compact_pattern_re = prefix? + * number_pattern_re + * suffix? + * + * number_pattern_re = 0+ + * + * Regexp groups: + * + * 0: compact_pattern_re + * 1: prefix + * 2: number_pattern_re (the number pattern to use in compact mode) + * 3: suffix + */ +return ( /^([^0]*)(0+)([^0]*)$/ ); + +}); diff --git a/src/number/compact.js b/src/number/compact.js index 52ae93a84..24771ca79 100644 --- a/src/number/compact.js +++ b/src/number/compact.js @@ -1,5 +1,5 @@ define([ - "./numbering-system" + "./numbering-system" ], function( numberNumberingSystem ) { /** @@ -12,11 +12,26 @@ define([ * Return the localized compact map for the given compact mode. */ return function( compactType, cldr ) { - return cldr.main([ - "numbers/decimalFormats-numberSystem-" + numberNumberingSystem( cldr ), - compactType, - "decimalFormat" - ]); + var maxExponent = 0; + + var object = cldr.main([ + "numbers/decimalFormats-numberSystem-" + numberNumberingSystem( cldr ), + compactType, + "decimalFormat" + ]); + + object = Object.keys( object ).reduce(function( newObject, compactKey ) { + var numberExponent = compactKey.split( "0" ).length - 1; + var pluralForm = compactKey.split( "-" )[ 2 ]; + newObject[ numberExponent ] = newObject[ numberExponent ] || {}; + newObject[ numberExponent ][ pluralForm ] = object[ compactKey ]; + maxExponent = Math.max( numberExponent, maxExponent ); + return newObject; + }, {}); + + object.maxExponent = maxExponent; + + return object; }; }); diff --git a/src/number/format-properties.js b/src/number/format-properties.js index 79a602499..0eee708db 100644 --- a/src/number/format-properties.js +++ b/src/number/format-properties.js @@ -65,6 +65,18 @@ return function( pattern, cldr, options ) { ]); if ( options.compact ) { + + // The compact digits number pattern is always `0+`, so override the following properties. + // Note: minimumIntegerDigits would actually range from `0` to `000` based on the scale of + // the value to be formatted, though we're always using 1 as a simplification, because the + // number won't be zero-padded since we chose the right format based on the scale, i.e., + // we'd never see something like `003M` anyway. + properties[ 2 ] = negativeSuffix[ 2 ] = 1; // minimumIntegerDigits + properties[ 3 ] = negativeSuffix[ 3 ] = 0; // minimumFractionDigits + properties[ 4 ] = negativeSuffix[ 4 ] = 0; // maximumFractionDigits + properties[ 5 ] = negativeSuffix[ 5 ] = // minimumSignificantDigits & + properties[ 6 ] = negativeSuffix[ 6 ] = undefined ; // maximumSignificantDigits + properties[20] = numberCompact( options.compact, cldr ); } diff --git a/src/number/format.js b/src/number/format.js index 2fc06bc6f..448ee0079 100644 --- a/src/number/format.js +++ b/src/number/format.js @@ -1,11 +1,13 @@ define([ + "./compact-pattern-re", "./format/grouping-separator", "./format/integer-fraction-digits", "./format/significant-digits", "./pattern-re", "../util/remove-literal-quotes" -], function( numberFormatGroupingSeparator, numberFormatIntegerFractionDigits, - numberFormatSignificantDigits, numberPatternRe, removeLiteralQuotes ) { +], function( numberCompactPatternRe, numberFormatGroupingSeparator, + numberFormatIntegerFractionDigits, numberFormatSignificantDigits, numberPatternRe, + removeLiteralQuotes ) { /** * format( number, properties ) @@ -68,29 +70,24 @@ return function( number, properties, pluralGenerator ) { number *= 1000; } - var compactPattern, compactDigits, compactProperties, divisor, pluralForm, zeroes, - originalNumber; + var compactPattern, compactDigits, compactProperties, divisor, numberExponent, pluralForm; // Compact mode: initial number digit processing if ( compactMap ) { - originalNumber = number; - zeroes = Array( Math.floor( number ).toString().length ).join( "0" ); - if ( zeroes.length >= 3 ) { - - // use default plural form to perform initial decimal shift - compactPattern = compactMap[ "1" + zeroes + "-count-other" ]; - - if ( compactPattern ) { - compactDigits = compactPattern.split( "0" ).length - 1; - divisor = zeroes.length - ( compactDigits - 1 ); - number = number / Math.pow( 10, divisor ); - - // Some languages specify no pattern for certain digit lengths, represented as "0". - // If no pattern, original number should remain uncompacted. - if ( compactPattern === "0" ) { - number = originalNumber; - } - } + numberExponent = Math.floor( number ).toString().length - 1; + numberExponent = Math.min( numberExponent, compactMap.maxExponent ); + + // Use default plural form to perform initial decimal shift + if ( numberExponent >= 3 ) { + compactPattern = compactMap[ numberExponent ] && compactMap[ numberExponent ].other; + } + + if ( compactPattern === "0" ) { + compactPattern = null; + } else if ( compactPattern ) { + compactDigits = compactPattern.split( "0" ).length - 1; + divisor = numberExponent - ( compactDigits - 1 ); + number = number / Math.pow( 10, divisor ); } } @@ -107,18 +104,16 @@ return function( number, properties, pluralGenerator ) { // Compact mode: apply formatting if ( compactMap && compactPattern ) { - pluralForm = pluralGenerator ? pluralGenerator( number ) : "other"; - compactPattern = compactMap[ "1" + zeroes + "-count-" + pluralForm ] || compactPattern; - // Some languages specify no pattern for certain digit lengths, represented as "0". - // Only apply compact pattern if one is specified. - if ( compactPattern !== "0" ) { - compactProperties = compactPattern.match( numberPatternRe ); + // Get plural form after possible roundings + pluralForm = pluralGenerator ? pluralGenerator( +number ) : "other"; - // update prefix/suffix with compact prefix/suffix - prefix += compactProperties[ 1 ]; - suffix = compactProperties[ 11 ] + suffix; - } + compactPattern = compactMap[ numberExponent ][ pluralForm ] || compactPattern; + compactProperties = compactPattern.match( numberCompactPatternRe ); + + // update prefix/suffix with compact prefix/suffix + prefix += compactProperties[ 1 ]; + suffix = compactProperties[ 3 ] + suffix; } // Remove the possible number minus sign diff --git a/src/number/pattern-re.js b/src/number/pattern-re.js index bbb0b0893..2d2b206b1 100644 --- a/src/number/pattern-re.js +++ b/src/number/pattern-re.js @@ -26,7 +26,7 @@ define(function() { * * suffix = non_number_stuff * - * non_number_stuff = regexp(.*?) + * non_number_stuff = regexp(('[^']+'|''|[^*#@0,.E])*) * * * Regexp groups: @@ -45,6 +45,6 @@ define(function() { * 11: suffix * 12: - */ -return ( /^(('([^']|'')*'|[^*#@0,.E])*)(\*.)?((([#,]*[0,]*0+)(\.0*[0-9]*#*)?)|([#,]*@+#*))(E\+?0+)?(.*?)$/ ); +return ( /^(('([^']|'')*'|[^*#@0,.E])*)(\*.)?((([#,]*[0,]*0+)(\.0*[0-9]*#*)?)|([#,]*@+#*))(E\+?0+)?(('[^']+'|''|[^*#@0,.E])*)$/ ); }); diff --git a/test/unit/number/format.js b/test/unit/number/format.js index b47d7217b..d880b4d09 100644 --- a/test/unit/number/format.js +++ b/test/unit/number/format.js @@ -44,12 +44,8 @@ zhSimplified = new Cldr( "zh" ); QUnit.module( "Number Format" ); -function oneOrOtherPluralGenerator( plural ) { - if ( plural === "1" ) { - return "one"; - } else { - return "other"; - } +function esPluralGenerator( n ) { + return ( n === 1 ) ? "one" : "other"; } /** @@ -81,123 +77,6 @@ QUnit.test( "should format negative integer", function( assert ) { assert.equal( format( -earthDiameter, properties( "0;(0.0##)", en ) ), "(12735)" ); }); -/** - * Compact Numbers - */ - -QUnit.test( "integers should format in compact mode", function( assert ) { - assert.equal( format( 273.7, properties( "#0", en, { compact: "short" } ) ), "274" ); - assert.equal( format( 273.7, properties( "#0", en, { compact: "long" } ) ), "274" ); - assert.equal( format( 273, properties( "#0", en, { compact: "short" } ) ), "273" ); - assert.equal( format( 273, properties( "#0", en, { compact: "long" } ) ), "273" ); - assert.equal( format( 573, properties( "#0", en, { compact: "short" } ) ), "573" ); - assert.equal( format( 573, properties( "#0", en, { compact: "long" } ) ), "573" ); - assert.equal( format( 1273, properties( "#0", en, { compact: "short" } ) ), "1K" ); - assert.equal( format( 1273, properties( "#0", en, { compact: "long" } ) ), "1 thousand" ); - assert.equal( format( 1273000, properties( "#0", es, { - compact: "long" - } ), oneOrOtherPluralGenerator ), "1 millón" ); - assert.equal( format( 2273000, properties( "#0", es, { - compact: "long" - } ), oneOrOtherPluralGenerator ), "2 millones" ); - assert.equal( format( 9999.9, properties( "#0", en, { compact: "long" } ) ), "10 thousand" ); - assert.equal( format( 12735, properties( "#0", en, { compact: "short" } ) ), "13K" ); - assert.equal( format( 12735, properties( "#0", en, { compact: "long" } ) ), "13 thousand" ); - assert.equal( format( 127350, properties( "#0", en, { compact: "short" } ) ), "127K" ); - assert.equal( format( 127350, properties( "#0", en, { compact: "long" } ) ), "127 thousand" ); - assert.equal( format( 1273500, properties( "#0", en, { compact: "short" } ) ), "1M" ); - assert.equal( format( 1273500, properties( "#0", en, { compact: "long" } ) ), "1 million" ); - assert.equal( format( -1273500, properties( "#0", en, { compact: "short" } ) ), "-1M" ); - assert.equal( format( -1273500, properties( "#0", en, { compact: "long" } ) ), "-1 million" ); - assert.equal( format( -1273500, properties( "#0;(#0)", en, { compact: "short" } ) ), "(1M)" ); - assert.equal( format( -1273500, properties( "#0;(#0)", en, { compact: "long" } ) ), "(1 million)" ); - - // some hungarian short formats have a terminating E, which is treated as a special - // character in non-compact formats - // \u00A0 is a unicode non-breaking space - assert.equal( format( 1273, properties( "#0", hu, { compact: "short" } ) ), "1\u00A0E" ); - assert.equal( format( 1273, properties( "#0", hu, { compact: "long" } ) ), "1 ezer" ); - assert.equal( format(9000000, properties( "#0", hu, { compact: "short" } ) ), "9\u00A0M" ); - assert.equal( format(9000000, properties( "#0", hu, { compact: "long" } ) ), "9 millió" ); -}); - -QUnit.test( "unsupported numbers should apply no compacting in compact mode", function( assert ) { - assert.equal( format( 0.01, properties( "#0.##", en, { compact: "short" } ) ), "0.01" ) - assert.equal( format( 1234000000000000, properties( "#0", en, { compact: "short" } ) ), "1234000000000000" ) -}); - -QUnit.test( "decimals should format in compact mode", function( assert ) { - assert.equal( format( 273.7, properties( "#0.#", en, { compact: "short" } ) ), "273.7" ); - assert.equal( format( 273.7, properties( "#0.#", en, { compact: "long" } ) ), "273.7" ); - assert.equal( format( 273, properties( "#0.#", en, { compact: "short" } ) ), "273" ); - assert.equal( format( 273, properties( "#0.#", en, { compact: "long" } ) ), "273" ); - assert.equal( format( 573, properties( "#0.#", en, { compact: "short" } ) ), "573" ); - assert.equal( format( 573, properties( "#0.#", en, { compact: "long" } ) ), "573" ); - assert.equal( format( 1273, properties( "#0.#", en, { compact: "short" } ) ), "1.3K" ); - assert.equal( format( 1273, properties( "#0.#", en, { compact: "long" } ) ), "1.3 thousand" ); - assert.equal( format( 1273000, properties( "#0.#", es, { - compact: "long" - } ), oneOrOtherPluralGenerator ), "1,3 millones" ); - assert.equal( format( 2273000, properties( "#0.#", es, { - compact: "long" - } ), oneOrOtherPluralGenerator ), "2,3 millones" ); - assert.equal( format( 9999.9, properties( "#0.#", en, { compact: "long" } ) ), "10 thousand" ); - assert.equal( format( 12735, properties( "#0.#", en, { compact: "short" } ) ), "12.7K" ); - assert.equal( format( 12735, properties( "#0.#", en, { compact: "long" } ) ), "12.7 thousand" ); - assert.equal( format( 127350, properties( "#0.#", en, { compact: "short" } ) ), "127.4K" ); - assert.equal( format( 127350, properties( "#0.#", en, { compact: "long" } ) ), "127.4 thousand" ); - assert.equal( format( 1273500, properties( "#0.#", en, { compact: "short" } ) ), "1.3M" ); - assert.equal( format( 1273500, properties( "#0.#", en, { compact: "long" } ) ), "1.3 million" ); - assert.equal( format( -1273500, properties( "#0.#", en, { compact: "short" } ) ), "-1.3M" ); - assert.equal( format( -1273500, properties( "#0.#", en, { compact: "long" } ) ), "-1.3 million" ); - assert.equal( format( -1273500, properties( "#0.#;(#0.#)", en, { compact: "short" } ) ), "(1.3M)" ); - assert.equal( format( -1273500, properties( "#0.#;(#0.#)", en, { compact: "long" } ) ), "(1.3 million)" ); -}); - -QUnit.test( "decimals should support rounding in compact mode", function( assert ) { - assert.equal( format( 12735, properties( "#0.##", en, { - compact: "short", - maximumFractionDigits: 2, - minimumFractionDigits: 1 - } ) ), "12.74K" ); - assert.equal( format( 12735, properties( "#0.##", en, { - compact: "short", - maximumFractionDigits: 2, - minimumFractionDigits: 1, - round: "ceil" - } ) ), "12.74K" ); - assert.equal( format( 12735, properties( "#0.##", en, { - compact: "short", - maximumFractionDigits: 2, - minimumFractionDigits: 1, - round: "floor" - } ) ), "12.73K" ); - assert.equal( format( 12735, properties( "#0.##", en, { - compact: "short", - maximumFractionDigits: 2, - minimumFractionDigits: 1, - round: "truncate" - } ) ), "12.73K" ); -}); - -QUnit.test( "integers should support significant digits in compact mode", function( assert ) { - assert.equal( format( 12735, properties( "@@@", en, { compact: "short" } ) ), "12.7K" ); - assert.equal( format( 12735, properties( "@@", en, { compact: "short" } ) ), "13K" ); - assert.equal( format( 12735, properties( "@", en, { compact: "short" } ) ), "10K" ); -}); - -QUnit.test( "integers should handle default (no) pattern in compact mode", function( assert ) { - assert.equal( format( 2737, properties( "#0.#", zhSimplified, { compact: "short" } ) ), "2737" ); - assert.equal( format( 2737, properties( "#0.#", zhSimplified, { compact: "long" } ) ), "2737" ); - assert.equal( format( 27371, properties( "#0.#", zhSimplified, { compact: "short" } ) ), "2.7万" ); - assert.equal( format( 27371, properties( "#0.#", zhSimplified, { compact: "long" } ) ), "2.7万" ); -}); - -QUnit.test( "percents should format in compact mode", function( assert ) { - assert.equal( format( 127, properties( "#0%", en, { compact: "short" } ) ), "13K%" ); - assert.equal( format( 127, properties( "#0%", en, { compact: "long" } ) ), "13 thousand%" ); -}); - /** * Decimals */ @@ -427,9 +306,207 @@ QUnit.test( "should format infinite numbers", function( assert ) { QUnit.test( "should format literal (')", function( assert ) { assert.equal( format( 69900, properties( "'$'#,##0", en ) ), "$69,900" ); + assert.equal( format( 69900, properties( "'$'#,##0.00", en ) ), "$69,900.00" ); // Make sure quoted characters (in this case, minus sign) aren't localized. assert.equal( format( -pi, properties( "0.##;'-'0.##", fa ) ), "-۳٫۱۴" ); }); +/** + * Compact Numbers + */ + +QUnit.test( "should format numbers in compact mode", function( assert ) { + assert.equal( format( 273.7, properties( "0", en, { compact: "short" } ) ), "274" ); + assert.equal( format( 273.7, properties( "0", en, { compact: "long" } ) ), "274" ); + assert.equal( format( 273, properties( "0", en, { compact: "short" } ) ), "273" ); + assert.equal( format( 273, properties( "0", en, { compact: "long" } ) ), "273" ); + assert.equal( format( 573, properties( "0", en, { compact: "short" } ) ), "573" ); + assert.equal( format( 573, properties( "0", en, { compact: "long" } ) ), "573" ); + assert.equal( format( 1273, properties( "0", en, { compact: "short" } ) ), "1K" ); + assert.equal( format( 1273, properties( "0", en, { compact: "long" } ) ), "1 thousand" ); + assert.equal( format( 1234000000000000, properties( "0", en, { compact: "short" } ) ), "1234T" ); + assert.equal( format( 1273000, properties( "0", es, { + compact: "long" + } ), esPluralGenerator ), "1 millón" ); + assert.equal( format( 2273000, properties( "0", es, { + compact: "long" + } ), esPluralGenerator ), "2 millones" ); + assert.equal( format( 27371, properties( "0", zhSimplified, { compact: "short" } ) ), "3万" ); + assert.equal( format( 27371, properties( "0", zhSimplified, { compact: "long" } ) ), "3万" ); + assert.equal( format( 9999.9, properties( "0", en, { compact: "long" } ) ), "10 thousand" ); + assert.equal( format( 12735, properties( "0", en, { compact: "short" } ) ), "13K" ); + assert.equal( format( 12735, properties( "0", en, { compact: "long" } ) ), "13 thousand" ); + assert.equal( format( 127350, properties( "0", en, { compact: "short" } ) ), "127K" ); + assert.equal( format( 127350, properties( "0", en, { compact: "long" } ) ), "127 thousand" ); + assert.equal( format( 1273500, properties( "0", en, { compact: "short" } ) ), "1M" ); + assert.equal( format( 1273500, properties( "0", en, { compact: "long" } ) ), "1 million" ); + assert.equal( format( -1273500, properties( "0", en, { compact: "short" } ) ), "-1M" ); + assert.equal( format( -1273500, properties( "0", en, { compact: "long" } ) ), "-1 million" ); + assert.equal( format( -1273500, properties( "0;(0)", en, { compact: "short" } ) ), "(1M)" ); + assert.equal( format( -1273500, properties( "0;(0)", en, { compact: "long" } ) ), "(1 million)" ); + + // Some hungarian short formats have a terminating E, which is treated as a special + // character in non-compact formats. + // \u00A0 is a unicode non-breaking space. + assert.equal( format( 1273, properties( "0", hu, { compact: "short" } ) ), "1\u00A0E" ); + assert.equal( format( 1273, properties( "0", hu, { compact: "long" } ) ), "1 ezer" ); + assert.equal( format( 9000000, properties( "0", hu, { compact: "short" } ) ), "9\u00A0M" ); + assert.equal( format( 9000000, properties( "0", hu, { compact: "long" } ) ), "9 millió" ); +}); + +QUnit.test( "should format numbers that lack pattern in compact mode (use default compact mode pattern)", function( assert ) { + assert.equal( format( 0.01, properties( "0", en, { compact: "short" } ) ), "0" ); + assert.equal( format( 273.7, properties( "0", en, { compact: "short" } ) ), "274" ); + assert.equal( format( 273.7, properties( "0", en, { compact: "long" } ) ), "274" ); + assert.equal( format( 2737, properties( "0", zhSimplified, { compact: "short" } ) ), "2737" ); + assert.equal( format( 2737, properties( "0", zhSimplified, { compact: "long" } ) ), "2737" ); +}); + +QUnit.test( "numbers should support fraction digits in compact mode", function( assert ) { + assert.equal( format( 1273, properties( "0", en, { + compact: "short", + maximumFractionDigits: 1, + minimumFractionDigits: 1 + })), "1.3K" ); + assert.equal( format( 1273, properties( "0", en, { + compact: "long", + maximumFractionDigits: 1, + minimumFractionDigits: 1 + })), "1.3 thousand" ); + assert.equal( format( 1273000, properties( "0", es, { + compact: "long", + maximumFractionDigits: 1, + minimumFractionDigits: 1 + }), esPluralGenerator ), "1,3 millones" ); + assert.equal( format( 2273000, properties( "0", es, { + compact: "long", + maximumFractionDigits: 1, + minimumFractionDigits: 1 + } ), esPluralGenerator ), "2,3 millones" ); + assert.equal( format( 12735, properties( "0", en, { + compact: "short", + maximumFractionDigits: 1, + minimumFractionDigits: 1 + })), "12.7K" ); + assert.equal( format( 12735, properties( "0", en, { + compact: "long", + maximumFractionDigits: 1, + minimumFractionDigits: 1 + })), "12.7 thousand" ); + assert.equal( format( 127350, properties( "0", en, { + compact: "short", + maximumFractionDigits: 1, + minimumFractionDigits: 1 + })), "127.4K" ); + assert.equal( format( 127350, properties( "0", en, { + compact: "long", + maximumFractionDigits: 1, + minimumFractionDigits: 1 + })), "127.4 thousand" ); + assert.equal( format( 1273500, properties( "0", en, { + compact: "short", + maximumFractionDigits: 1, + minimumFractionDigits: 1 + })), "1.3M" ); + assert.equal( format( 1273500, properties( "0", en, { + compact: "long", + maximumFractionDigits: 1, + minimumFractionDigits: 1 + })), "1.3 million" ); + assert.equal( format( -1273500, properties( "0", en, { + compact: "short", + maximumFractionDigits: 1, + minimumFractionDigits: 1 + })), "-1.3M" ); + assert.equal( format( -1273500, properties( "0", en, { + compact: "long", + maximumFractionDigits: 1, + minimumFractionDigits: 1 + })), "-1.3 million" ); + assert.equal( format( -1273500, properties( "0;(0)", en, { + compact: "short", + maximumFractionDigits: 1, + minimumFractionDigits: 1 + })), "(1.3M)" ); + assert.equal( format( -1273500, properties( "0;(0)", en, { + compact: "long", + maximumFractionDigits: 1, + minimumFractionDigits: 1 + })), "(1.3 million)" ); +}); + +QUnit.test( "numbers should support significant digits in compact mode", function( assert ) { + assert.equal( format( 12735, properties( "0", en, { + compact: "short", + maximumSignificantDigits: 3, + minimumSignificantDigits: 3 + })), "12.7K" ); + assert.equal( format( 12735, properties( "0", en, { + compact: "short", + maximumSignificantDigits: 2, + minimumSignificantDigits: 2 + })), "13K" ); + assert.equal( format( 12735, properties( "0", en, { + compact: "short", + maximumSignificantDigits: 1, + minimumSignificantDigits: 1 + })), "10K" ); +}); + +QUnit.test( "numbers should support rounding in compact mode", function( assert ) { + assert.equal( format( 12735, properties( "0", en, { + compact: "short", + maximumFractionDigits: 2, + minimumFractionDigits: 1 + } ) ), "12.74K" ); + assert.equal( format( 12735, properties( "0", en, { + compact: "short", + maximumFractionDigits: 2, + minimumFractionDigits: 1, + round: "ceil" + } ) ), "12.74K" ); + assert.equal( format( 12735, properties( "0", en, { + compact: "short", + maximumFractionDigits: 2, + minimumFractionDigits: 1, + round: "floor" + } ) ), "12.73K" ); + assert.equal( format( 12735, properties( "0", en, { + compact: "short", + maximumFractionDigits: 2, + minimumFractionDigits: 1, + round: "truncate" + } ) ), "12.73K" ); +}); + +QUnit.test( "percents should format in compact mode", function( assert ) { + assert.equal( format( 127, properties( "0%", en, { compact: "short" } ) ), "13K%" ); + assert.equal( format( 127, properties( "0%", en, { compact: "long" } ) ), "13 thousand%" ); +}); + +QUnit.test( "given pattern properties should be ignored in compact mode", function( assert ) { + + // minimumIntegerDigits, minimumFractionDigits, maximumFractionDigits, minimumSignificantDigits, + // maximumSignificantDigits extracted from the pattern should be ignored. + assert.equal( format( 2737, properties( "0.#", en, { compact: "short" } ) ), "3K" ); + assert.equal( format( 2737, properties( "0.0", en, { compact: "short" } ) ), "3K" ); + assert.equal( format( 2737, properties( "0.0#", en, { compact: "short" } ) ), "3K" ); + assert.equal( format( 2737, properties( "@@", en, { compact: "short" } ) ), "3K" ); + assert.equal( format( 69900, properties( "'$'#,##0.00", en, {compact: "short"} ) ), "$70K" ); + + assert.equal( format( 12.01, properties( "0.#", en, { compact: "short" } ) ), "12" ); + assert.equal( format( 12.01, properties( "0.#", en, { compact: "short" } ) ), "12" ); + assert.equal( format( 12.01, properties( "0.0", en, { compact: "short" } ) ), "12" ); + assert.equal( format( 12.01, properties( "0.0#", en, { compact: "short" } ) ), "12" ); + assert.equal( format( 12.01, properties( "@@", en, { compact: "short" } ) ), "12" ); + assert.equal( format( 12.01, properties( "'$'#,##0.00", en, {compact: "short"} ) ), "$12" ); + + // Preserve grouping separator from original pattern. (only used in very big numbers) + assert.equal( format( 1234000000000000, properties( "#,##0", en, { compact: "short" } ) ), "1,234T" ); + + // Preserve prefix and suffix + assert.equal( format( 2737, properties( "(0.0#)", en, { compact: "short" } ) ), "(3K)" ); +}); + });