UNPKG

aveta

Version:

Convert long numbers into abbreviated and human-readable strings.

148 lines 5.57 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.avetaReverse = exports.aveta = void 0; const options_1 = require("./options"); const DIGIT_BASE = 1000; /** * Generator that divides a number until a decimal value is found. * * e.g. 1,000,000 is grouped in multiples of 1000. */ function* generator(value) { // Create a mutable copy of the base. let divisor = DIGIT_BASE; while (true) { const result = value / divisor; if (result < 1) { // We can't divide the value any further. return; } yield result; divisor *= DIGIT_BASE; } } /** * parseValue ensures the value is a number and within accepted range. */ function parseValue(value) { const val = parseFloat(value.toString()); if (isNaN(val)) { throw new Error(`Input value is not a number`); } if (val > Number.MAX_SAFE_INTEGER || val < Number.MIN_SAFE_INTEGER) { throw new RangeError('Input value is outside of safe integer range'); } return val; } /** * Rounds a number [value] up to a specified [precision]. */ function roundTo(value, { precision, digits, roundingMode = 'nearest', }) { if (!Number.isFinite(value)) { throw new Error('Input value is not an infinite number'); } if (!Number.isInteger(precision) || precision < 0) { throw new Error('Precision is not a positive integer'); } if (!Number.isInteger(digits) || digits < 0) { throw new Error('Digits is not a positive integer'); } if (Number.isInteger(value)) { return value; } const result = digits > 0 ? parseFloat(value.toPrecision(digits)) : parseFloat(value.toFixed(precision)); const multiplier = Math.pow(10, precision); const shifted = result * multiplier; switch (roundingMode) { case 'up': return Math.ceil(shifted) / multiplier; case 'down': return Math.floor(shifted) / multiplier; default: // 'nearest' return Math.round(shifted) / multiplier; } } /** * aveta converts long numbers to human-readable strings in an easy way. */ function aveta(value, options) { var _a; // Override default options with options supplied by user. const opts = options ? Object.assign(Object.assign({}, options_1.Options), options) : options_1.Options; if (!Array.isArray(opts.units) || !opts.units.length) { throw new Error('Option `units` must be a non-empty array'); } // Validate value for type and length. let val = parseValue(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 generator(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) { return value.toString(); } // Round decimal up to desired precision. const { precision, digits, roundingMode = 'nearest' } = opts; let rounded = roundTo(val, { precision, digits, roundingMode, }); // The rounded value needs another iteration in the generator(divider) cycle. for (const result of generator(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 ? ' ' : ''; // Replace decimal mark if desired. const formatted = rounded .toString() .replace(options_1.Options.separator, opts.separator); return `${prefix}${formatted}${space}${suffix}`; } exports.aveta = aveta; function avetaReverse(formattedValue, options) { var _a; const opts = options ? Object.assign(Object.assign({}, options_1.Options), options) : options_1.Options; // Remove any spaces and convert to uppercase for consistency const cleanedValue = formattedValue.replace(/\s/g, '').toUpperCase(); // Extract the numeric part and the unit const match = cleanedValue.match(/^(-?\(?)([\d.,]+)([A-Z]*)(\)?)?$/); if (!match) { throw new Error('Invalid formatted value'); } const [, sign, numericPart, unit] = match; // Parse the numeric part const number = parseFloat(numericPart.replace(opts.separator, '.')); // Find the unit index const unitIndex = opts.units.findIndex((u) => u.toUpperCase() === unit); if (unitIndex === -1 && unit !== '') { throw new Error('Unknown unit'); } let originalValue = number * Math.pow((_a = opts.base) !== null && _a !== void 0 ? _a : 1000, unitIndex); // Adjust for potential rounding errors const magnitude = Math.pow(10, opts.precision); originalValue = Math.round(originalValue * magnitude) / magnitude; // Apply the sign return sign === '-' || sign === '(' ? -originalValue : originalValue; } exports.avetaReverse = avetaReverse; exports.default = aveta; //# sourceMappingURL=aveta.js.map