Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Numbers: support compact mode (2/2) #759

Merged
merged 2 commits into from
Aug 22, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
39 changes: 39 additions & 0 deletions doc/api/currency/currency-formatter.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -37,6 +39,8 @@ formatter( 9.99 );

```

#### Instance Formatter

You can use the instance method `.currencyFormatter()`, which uses the instance locale.

```javascript
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -131,6 +139,37 @@ formatter( 1.491 );
// > "$1.50"
```

#### Formatting Compact Currencies

```js
var shortFormatter = Globalize( "en" ).currencyFormatter( "USD", {
compact: "short"
});

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
Expand Down
35 changes: 35 additions & 0 deletions doc/api/number/number-formatter.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -139,6 +143,37 @@ 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"
});

var longFormatter = Globalize( "en" ).numberFormatter({
compact: "long"
});

shortFormatter( 27588910 );
// > "28M"

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`.
Expand Down
13 changes: 9 additions & 4 deletions src/number.js
Original file line number Diff line number Diff line change
Expand Up @@ -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" );

Expand All @@ -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;
};
Expand Down
21 changes: 21 additions & 0 deletions src/number/compact-pattern-re.js
Original file line number Diff line number Diff line change
@@ -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]*)$/ );

});
37 changes: 37 additions & 0 deletions src/number/compact.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
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 ) {
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;
};

});
22 changes: 20 additions & 2 deletions src/number/format-properties.js
Original file line number Diff line number Diff line change
@@ -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] )
Expand Down Expand Up @@ -63,6 +64,22 @@ return function( pattern, cldr, options ) {
numberNumberingSystemDigitsMap( cldr )
]);

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 );
}

getOptions( "minimumIntegerDigits", 2 );
getOptions( "minimumFractionDigits", 3 );
getOptions( "maximumFractionDigits", 4 );
Expand Down Expand Up @@ -98,6 +115,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;
};

Expand Down
57 changes: 48 additions & 9 deletions src/number/format.js
Original file line number Diff line number Diff line change
@@ -1,10 +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, removeLiteralQuotes ) {
], function( numberCompactPatternRe, numberFormatGroupingSeparator,
numberFormatIntegerFractionDigits, numberFormatSignificantDigits, numberPatternRe,
removeLiteralQuotes ) {

/**
* format( number, properties )
Expand All @@ -16,11 +19,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 ];
Expand All @@ -36,6 +39,7 @@ return function( number, properties ) {
nanSymbol = properties[ 17 ];
symbolMap = properties[ 18 ];
nuDigitsMap = properties[ 19 ];
compactMap = properties[ 20 ];

// NaN
if ( isNaN( number ) ) {
Expand All @@ -57,8 +61,6 @@ return function( number, properties ) {
return prefix + infinitySymbol + suffix;
}

ret = prefix;

// Percent
if ( pattern.indexOf( "%" ) !== -1 ) {
number *= 100;
Expand All @@ -68,6 +70,27 @@ return function( number, properties ) {
number *= 1000;
}

var compactPattern, compactDigits, compactProperties, divisor, numberExponent, pluralForm;

// Compact mode: initial number digit processing
if ( compactMap ) {
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 );
}
}

// Significant digit format
if ( !isNaN( minimumSignificantDigits * maximumSignificantDigits ) ) {
number = numberFormatSignificantDigits( number, minimumSignificantDigits,
Expand All @@ -79,6 +102,20 @@ return function( number, properties ) {
minimumFractionDigits, maximumFractionDigits, round, roundIncrement );
}

// Compact mode: apply formatting
if ( compactMap && compactPattern ) {

// Get plural form after possible roundings
pluralForm = pluralGenerator ? pluralGenerator( +number ) : "other";

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
number = number.replace( /^-/, "" );

Expand All @@ -88,6 +125,8 @@ return function( number, properties ) {
secondaryGroupingSize );
}

ret = prefix;

ret += number;

// Scientific notation
Expand Down
4 changes: 2 additions & 2 deletions src/number/formatter-fn.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
};
};

Expand Down
Loading