UNPKG

millify

Version:

Converts long numbers to pretty, human-readable strings

97 lines (96 loc) 3.85 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.millify = void 0; const options_1 = require("./options"); const utils_1 = require("./utils"); // Most commonly used digit grouping base. const DIGIT_GROUPING_BASE = 1000; /** * Generator that divides a number until a decimal value is found. * * The denominator is defined by the numerical digit grouping base, * or interval. The most commonly-used digit group interval is 1000. * * e.g. 1,000,000 is grouped in multiples of 1000. */ function* divider(value) { // Create a mutable copy of the base. let denominator = DIGIT_GROUPING_BASE; while (true) { const result = value / denominator; if (result < 1) { // End of operation. We can't divide the value any further. return; } yield result; // The denominator is increased every iteration by multiplying // the base by itself, until a decimal value remains. denominator *= DIGIT_GROUPING_BASE; } } /** * millify converts long numbers to human-readable strings. */ function millify(value, options) { var _a, _b; // Override default options with options supplied by user. const opts = options ? { ...options_1.defaultOptions, ...options } : options_1.defaultOptions; if (!Array.isArray(opts.units) || !opts.units.length) { throw new Error("Option `units` must be a non-empty array"); } // If the input value is invalid, then return the value in string form. // Originally this threw an error, but was changed to return a graceful fallback. let val; try { val = utils_1.parseValue(value); } catch (e) { if (e instanceof Error) { console.warn(`WARN: ${e.message} (millify)`); } // Invalid values will be converted to string as per `String()`. return String(value); } // Add a minus sign (-) prefix if it's a negative number. const prefix = val < 0 ? "-" : ""; // Work only with positive values for simplicity's sake. val = Math.abs(val); // Keep dividing the input value by the digit grouping base // until the decimal and the unit index is deciphered. let unitIndex = 0; for (const result of divider(val)) { val = result; unitIndex += 1; } // Return the original number if the number is too large to have // a corresponding unit. Returning anything else is ambiguous. const unitIndexOutOfRange = unitIndex >= opts.units.length; if (unitIndexOutOfRange) { // At this point we don't know what to do with the input value, // so we return it as is, without localizing the string. return value.toString(); } // Round decimal up to desired precision. let rounded = utils_1.roundTo(val, opts.precision); // Fixes an edge case bug that outputs certain numbers as 1000K instead of 1M. // The rounded value needs another iteration in the divider cycle. for (const result of divider(rounded)) { rounded = result; unitIndex += 1; } // Calculate the unit suffix and make it lowercase (if needed). const unit = (_a = opts.units[unitIndex]) !== null && _a !== void 0 ? _a : ""; const suffix = opts.lowercase ? unit.toLowerCase() : unit; // Add a space between number and abbreviation. const space = opts.space ? " " : ""; // Format the number according to the desired locale. const formatted = rounded.toLocaleString((_b = opts.locales) !== null && _b !== void 0 ? _b : utils_1.getLocales(), { // toLocaleString needs the explicit fraction digits. minimumFractionDigits: utils_1.getFractionDigits(rounded), }); return `${prefix}${formatted}${space}${suffix}`; } exports.millify = millify; exports.default = millify;