formatters/number.js

/**
 * @fileoverview Number format library.
 *
 * @see https://google.github.io/styleguide/javascriptguide.xml
 * @see https://developers.google.com/closure/compiler/docs/js-for-compiler
 * @module glize/formatters/number
 */


/**
 * Formats given number according to given options.
 * @param {number} number The number to be formatted.
 * @param {!Object<string, *>=} opt_options Formatting options.
 * @return {string} The formatted number string.
 * @method
 * @example
 * formatNumber(100);   // 100
 * formatNumber(1000);  // 1,000
 * formatNumber(1500);  // 1,500
 * formatNumber(10000); // 10,000
 * formatNumber(1e6);   // 1,000,000
 * const options = {'prefix': '$'};
 * formatNumber(100, options);   // $100
 * formatNumber(1e6, options);   // $1,000,000
 */
export const formatNumber = (number, opt_options) => {
  const options = getOptions_(opt_options);
  const result = (options['fraction'] ? 
      number.toFixed(options['fraction']) :
      '' + number).split('.');

  return options['prefix'] +
      result[0].replace(/\B(?=(\d{3})+(?!\d))/g,
      /** @type {string} */ (options['grouping'])) +
      (result[1] ? options['decimal'] + result[1] : '') +
      options['suffix'];
};

/**
 * Rounds given number.
 * @param {number} number The number to be rounded.
 * @param {!Object<string, *>=} opt_options Formatting options.
 * @return {string} The rounded number string.
 * @method
 * @example
 * roundNumber(100);   // 100
 * roundNumber(1000);  // 1k
 * roundNumber(1500);  // 1.5k
 * roundNumber(10000); // 10k
 * roundNumber(1e6);   // 10m
 * const options = {'prefix': '$'};
 * roundNumber(100, options);   // $100
 * roundNumber(1e6, options);   // $10m
 */
export const roundNumber = (number, opt_options) => {
  const options = getOptions_(opt_options);
  // parseInt(number, 10) == ~~number
  const base = (~~number + '').length - (number < 0 ? 2 : 1);
  const length = ~~(base / 3) * 3;
  let divider = 1;

  for (let i = 0; i < length; ++i) {
    divider *= 10;
  }

  const delta = number / divider;
  const a = ~~delta;
  const b = delta.toFixed(2 - base % 3);

  const result = (a == b ? a : b) + ['', 'k', 'm', 'g', 't'][~~(base / 3)];
  return options['prefix'] + result + options['suffix'];
};

/**
 * Gets the ordinal suffix for a number.
 * @param {number} number The number.
 * @return {string} Returns the ordinal suffix for a number.
 * @method
 */
export const ordinal = (number) => {
  const index = (
      number = ~~(number < 0 ? -number : number) % 100) > 10 &&
      number < 14 || (number %= 10) > 3 ? 0 : number;

  return ['th', 'st', 'nd', 'rd'][index];
};

/**
 * @param {!Object<string, *>=} options Formatting options.
 * @return {!Object<string, *>} Returns formatting options.
 * @private
 */
const getOptions_ = (options = {}) => {
  for (let key in defaults_) {
    if (!(key in options)) {
      options[key] = defaults_[key];
    }
  }
  return options;
};

/**
 * Default formatting options.
 * @type {!Object<string, *>}
 * @private
 */
const defaults_ = {
  'decimal': '.',
  'grouping': ',',
  'fraction': 0,
  'prefix': '',
  'suffix': ''
};