UNPKG

xtutils

Version:

Thuku's assorted general purpose typescript/javascript library.

521 lines 19.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports._clamp = exports._parse_float = exports._parse_int = exports._numk = exports._logx = exports._distance = exports._rad2deg = exports._deg2rad = exports._base2dec = exports._oct2dec = exports._dec2oct = exports._hex2dec = exports._dec2hex = exports._bin2dec = exports._dec2bin = exports._dec2base = exports._bytesVal = exports._px2rem = exports._rand = exports._commas = exports._round = exports._posInt = exports._int = exports._posNum = exports._num = exports._numeric = void 0; /** * Check if value is numeric * * @param value Parse value * @param booleans Pass `boolean` values as numeric * @param blanks Pass empty `string` values (because `!isNaN('') === true`) * @returns `boolean` is numeric */ const _numeric = (value, booleans = false, blanks = false) => { if ('number' === typeof value) return !isNaN(value); if ('boolean' === typeof value) return !!booleans; const v = String(value).trim(); if (v === '') return !!blanks; return /(^[+-]?[0-9]+([.][0-9]+)?([eE][+-]?[0-9]+)?$)|(^[+-]?\.[0-9]+$)|(^[+-]?[0-9]+\.$)/.test(v); }; exports._numeric = _numeric; /** * Get parsed and normalized `number` * * - trims `string` value and `''` => `NaN` * - supports (#/#.#/.#/#.) & comma separated/spaced string (i.e. `'1, 200, 000 . 3455'` => `1200000.3455`) * - normalizes float `3+` last zeros from `5th` place (i.e. `1.1/100` = `0.011000000000000001` => `0.011`) * * @param value - parse number value * @param _default - default `number` result when invalid (default `NaN`) * @returns `number` | `NaN` when invalid or when `''` */ const _num = (value, _default = NaN) => { // parse string value if ('string' === typeof value) { // parse filled, single line text if ((value = value.trim()) && /^.*$/.test(value)) { // match leading +/- operator prefix let prefix = ''; let match = value.trim().match(/^([\+-])\s*(\d.*)$/); if (match) { prefix = match[1]; // +|- value = match[2]; // value } // remove whitespace around [\d,\.] value = value.replace(/\s*([\.,])\s*/g, '$1'); // match & remove "," thousand separator if (value.match(/^\d{1,3}(,\d{3})*(\.|(\.\d+))?$/)) value = value.replace(/,/g, '').trim(); // validate number format - allow (#/#.#/.#/#.) if (/^\d+\.$|^\.\d+$|^\d+(\.\d+){0,1}$/.test(value)) { // parse number & restore +/- operator prefix if (!isNaN(value = parseFloat(value)) && prefix) value = parseFloat(prefix + value); } else value = NaN; } else value = NaN; // invalid number string } else value = Number(value); // coerce number // valid safe number => result let res; if (!isNaN(value = Number(value)) && value >= Number.MIN_SAFE_INTEGER && value <= Number.MAX_SAFE_INTEGER) { // check & normalize float `3+` last zeros from 5th place ~ 0.011000000000000001 => 0.011 let match = String(value).match(/^([\+-]?\d+\.\d{5,})(0{3,}\d*)$/); if (match) value = Number(match[1]); // result res = value; } else res = _default; // invalid => default result return isNaN(res) ? _default : (!res ? 0 : res); }; exports._num = _num; /** * Get parsed safe positive `number` with optional within min/max limit check * * @param value - parse number value * @param min - set min limit ~ enabled when `min` is a valid positive number * @param max - set max limit ~ enabled when `max` is a valid positive number * @returns `number` positive | `undefined` when invalid or out of `min/max` bounds */ const _posNum = (value, min, max) => { const val = (0, exports._num)(value); if (!(!isNaN(val) && val >= 0)) return undefined; if ('number' === typeof min && !isNaN(min) && min >= 0 && val < min) return undefined; if ('number' === typeof max && !isNaN(max) && max >= 0 && val > max) return undefined; return val; }; exports._posNum = _posNum; /** * Get parsed safe `integer` value * * @param value - parse number value * @param _default - result `number` when invalid (default `NaN`) * @returns `number` integer */ const _int = (value, _default = NaN) => { const val = Math.floor((0, exports._num)(value, _default)); return !isNaN(val) ? val : _default; }; exports._int = _int; /** * Get parsed safe positive `integer` value with optional within min/max limit check * * @param value - parse number value * @param min - set min limit ~ enabled when `min` is a valid positive number * @param max - set max limit ~ enabled when `max` is a valid positive number * @param _limit_default - (default: `false`) use min/max value when value goes beyond limit (e.g. `_posInt(150,0,100,true)` => `100`) * @returns `number` positive | `undefined` when invalid or out of `min/max` bounds */ const _posInt = (value, min, max, _limit_default = false) => { const val = (0, exports._int)(value); if (!(!isNaN(val) && val >= 0)) return undefined; if ('number' === typeof min && !isNaN(min) && min >= 0 && val < min) return _limit_default ? min : undefined; if ('number' === typeof max && !isNaN(max) && max >= 0 && val > max) return _limit_default ? max : undefined; return val; }; exports._posInt = _posInt; /** * Round number to decimal places * * @param value Parse value * @param places [default: `2`] Decimal places * @returns `number` rounded */ const _round = (value, places = 2) => { if (isNaN(value)) return NaN; let p = 10 ** Math.abs((0, exports._int)(places, 2)); return Math.round((value + Number.EPSILON) * p) / p; }; exports._round = _round; /** * Convert numeric value to comma thousand delimited string (i.e. `1000.4567` => `'1,000.45'`) * * @param value Parse value * @param places [default: `2`] Round decimal places * @param zeros Enable trailing `'0'` decimal places (i.e. `1000` => `'1,000.00'`) * @returns `string` Comma thousand delimited number (returns `""` if parsed `value` is `NaN`) */ const _commas = (value, places = 2, zeros = false) => { const num = (0, exports._round)((0, exports._num)(value), places = (0, exports._int)(places, 2)); if (isNaN(num)) { console.warn('[WARNING: `_commas`] NaN value:', value); return ''; } let val = String(num).replace(/\B(?=(\d{3})+(?!\d))/g, ','); if (places && zeros) { if (val.indexOf('.') === -1) val += '.'.padEnd(places + 1, '0'); else val = val.split('.').reduce((prev, v, i) => { prev.push(i === 1 && v.length < places ? v.padEnd(places, '0') : v); return prev; }, []).join('.'); } return val; }; exports._commas = _commas; /** * Generate random `integer` number. * * @param min Min `integer` * @param max Max `integer` * @returns `number` Random `integer` */ const _rand = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min; exports._rand = _rand; /** * Convert px to rem (or reverse) * * @param val - convert value [default: `1`] * @param reverse - convert rem to px * @param base - root px [default: `16`] * @returns `number` */ const _px2rem = (val = 1, reverse = false, base = 16) => { val = (0, exports._num)(val, 1); base = (0, exports._num)(base, 16); const unit = base === 16 ? 0.0625 : 16 / base * 0.0625; return reverse ? val / unit : val * unit; }; exports._px2rem = _px2rem; /** * Convert bytes to size value * * @param bytes - parse bytes * @param mode - parse result mode (default: `0`) * - `0` = `string` size text (e.g. `_bytesVal(2097152)` => `2 MB`) * - `1` = `number` size value (e.g. `_bytesVal(2097152,1,'MB',0)` => `2`) * @param unit - size unit (default: `undefined` = max) ~ `'B'|'KB'|'MB'|'GB'|'TB'|'PB'|'EB'|'ZB'|'YB'` * @param places - decimal places * @returns `number` */ const _bytesVal = (bytes, mode = 0, unit, places = 2, commas = false) => { mode = (0, exports._posInt)(mode, 0, 1) ?? 0; if (!(bytes = (0, exports._posInt)(bytes, 0) ?? 0)) return mode === 1 ? 0 : '0 B'; // -- zero const kb = 1024, units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; const u = 'string' === typeof unit && units.includes(unit = unit.trim().toUpperCase()) ? unit : ''; const i = u ? units.findIndex(v => v.toLowerCase() === u.toLowerCase()) : Math.floor(Math.log(bytes) / Math.log(kb)); if (!(i >= 0 && i < units.length)) return mode === 1 ? bytes : bytes + ' B'; // -- unsupported size (defaults to bytes) let val = bytes / Math.pow(kb, i); if (mode === 1) return (0, exports._round)(val, places); return (commas ? (0, exports._commas)(val, places) : (0, exports._round)(val, places)) + ' ' + units[i]; }; exports._bytesVal = _bytesVal; /** * Convert decimal to base * * @example * _dec2base(126, 2) // '1111110' * _dec2base(126, 2, 4) // '0111 1110' * _dec2base(126, 8) // '176' * _dec2base(126, 16) // '7E' * _dec2base(1000, 16) // '03E8' * _dec2base(1000, 16, 2) // '03 E8' * * @param decimal - parse decimal integer * @param base - to base (default: `2`) ~ `2` = binary, `8` - octal, `16` - hexadecimal * @param group - space group characters length (default: `0`) ~ enabled when base = `2|16` * @returns `string` */ const _dec2base = (decimal, base = 2, group = 0) => { let dec = (0, exports._posInt)(decimal, 0) ?? 0; if (dec === 0) return '0'; base = [2, 8, 16].includes(base = (0, exports._posInt)(base, 2) ?? 2) ? base : 2; const hex_chars = base === 16 ? '0123456789ABCDEF'.split('') : []; let val = ''; while (dec > 0) { let remainder = dec % base; val = (base === 16 ? hex_chars[remainder] : remainder) + val; dec = Math.floor(dec / base); } if ([2, 16].includes(base) && !!(group = (0, exports._posInt)(group, 0) ?? 0)) { let buffer = ''; while (val.length) { let i = val.length - group; buffer = val.substring(i).padStart(group, '0') + (buffer ? ' ' : '') + buffer; val = val.substring(0, i); } val = buffer; } return val; }; exports._dec2base = _dec2base; /** * Parse decimal to binary * * @example * _dec2bin(126) // 1111110 * _dec2bin(126, 4) // 0111 1110 * _dec2bin(126, 8) // 01111110 * * @param decimal - parse decimal integer * @param group - space group characters length (default: `0`) * @returns `string` binary text */ const _dec2bin = (decimal, group = 0) => (0, exports._dec2base)(decimal, 2, group); exports._dec2bin = _dec2bin; /** * Parse binary to decimal * * @example * _bin2dec('0111 1110') // 126 * * @param binary - parse binary text * @returns `number|undefined` ~ parsed decimal integer | `undefined` when invalid */ const _bin2dec = (binary) => { if (!('string' === typeof binary && /^[01]+$/.test(binary = binary.replace(/\s/g, '')))) return undefined; // -- invalid binary text let dec = 0, pow = 0; for (let i = binary.length - 1; i >= 0; i--) { dec += parseInt(binary[i]) * Math.pow(2, pow); pow++; } return dec; }; exports._bin2dec = _bin2dec; /** * Parse decimal to hexadecimal * * @example * _dec2hex(1000) // '03E8' * _dec2hex(1000, 2) // '03 E8' * * @param decimal - parse decimal integer `number` * @param group - space group characters length (default: `0`) * @returns `string` - hexadecimal text */ const _dec2hex = (decimal, group = 0) => (0, exports._dec2base)(decimal, 16, group); exports._dec2hex = _dec2hex; /** * Parse hexadecimal to decimal * * @example * _hex2dec('0x7E') // 126 * _hex2dec('03 E8') // 1000 * * @param hex - parse hexadecimal text * @returns `number|undefined` ~ parsed decimal integer | `undefined` when invalid */ const _hex2dec = (hex) => { if (!('string' === typeof hex && /^[0-9A-F]+$/.test(hex = hex.replace(/0x/ig, '').replace(/\s/g, '').toUpperCase()))) return undefined; // -- invalid hexadecimal text const hex_map = Object.fromEntries('0123456789ABCDEF'.split('').map((v, i) => [v, i])); let dec = 0; for (let i = 0; i < hex.length; i++) { const val = hex_map[hex[i]]; dec = dec * 16 + val; } return dec; }; exports._hex2dec = _hex2dec; /** * Parse decimal to octal * * @example * _dec2oct(126) // 176 * _dec2oct(512) // 1000 * * @param decimal - parse decimal integer `number` * @returns `string` - octal text */ const _dec2oct = (decimal) => (0, exports._dec2base)(decimal, 8); exports._dec2oct = _dec2oct; /** * Parse octal to decimal * * @example * _oct2dec('0o176') // 126 * _oct2dec('1000') // 512 * * @param octal - parse octal text * @returns `number|undefined` ~ parsed decimal integer | `undefined` when invalid */ const _oct2dec = (octal) => { if (!('string' === typeof octal && /^[0-7]+$/.test(octal = octal.replace(/0o/ig, '').replace(/\s/g, '').toUpperCase()))) return undefined; // -- invalid octal text let dec = 0; for (let i = 0; i < octal.length; i++) { const val = octal[i] - 0; dec = dec * 8 + val; } return dec; }; exports._oct2dec = _oct2dec; /** * Parse text from base to decimal * * @example * _base2dec('0111 1110', 2) // 126 * _base2dec('0o176', 8) // 126 * _base2dec('0x7E', 16) // 126 * * @param value - parse text * @param base - from base (default: `2`) ~ `2` = binary, `8` - octal, `16` - hexadecimal * @returns `number|undefined` ~ parsed decimal integer | `undefined` when invalid */ const _base2dec = (value, base = 2) => { base = [2, 8, 16].includes(base = (0, exports._posInt)(base, 2) ?? 2) ? base : 2; if (base === 2) return (0, exports._bin2dec)(value); else if (base === 8) return (0, exports._oct2dec)(value); return (0, exports._hex2dec)(value); }; exports._base2dec = _base2dec; /** * Convert degree to [radian](https://en.wikipedia.org/wiki/Radian) * - `2π rad = 360°` ∴ `radian = degree * π/180` * * @param degrees - angle in degrees (i.e. 0 - 360°) * @returns `number` - radian */ const _deg2rad = (degrees) => { if (isNaN(degrees = (0, exports._num)(degrees))) throw new TypeError('The _deg2rad `degrees` argument is not a valid angle number value.'); return degrees * (Math.PI / 180); }; exports._deg2rad = _deg2rad; /** * Convert radian to [degree](https://en.wikipedia.org/wiki/Degree_(angle)) * - `2π rad = 360°` ∴ `radian = degree * π/180` * * @param radians - angle in radians (i.e. 0 - 360°) * @returns `number` - degree */ const _rad2deg = (radians) => { if (isNaN(radians = (0, exports._num)(radians))) throw new TypeError('The _rad2deg `radians` argument is not a valid angle number value.'); return radians * (180 / Math.PI); }; exports._rad2deg = _rad2deg; /** * Get distance in meters between two latitude and longitude coordinates * * @param latitude1 - first coordinate latitude `number` * @param longitude1 - first coordinate longitude `number` * @param latitude2 - second coordinate latitude `number` * @param longitude2 - second coordinate longitude `number` * @returns `number` `m` distance * @throws `TypeError` when coorinate argument value is `NaN` */ const _distance = (latitude1, longitude1, latitude2, longitude2) => { if (isNaN(latitude1 = (0, exports._num)(latitude1))) throw new TypeError('The _latLonDistance `latitude1` argument is not a valid latitude number value.'); if (isNaN(longitude1 = (0, exports._num)(longitude1))) throw new TypeError('The _latLonDistance `longitude1` argument is not a valid longitude number value.'); if (isNaN(latitude2 = (0, exports._num)(latitude2))) throw new TypeError('The _latLonDistance `latitude2` argument is not a valid latitude number value.'); if (isNaN(longitude2 = (0, exports._num)(longitude2))) throw new TypeError('The _latLonDistance `longitude2` argument is not a valid longitude number value.'); // const R = 6371e3; // Earth radius in meters const R = 6.378e+6; // Earth radius in meters const φ1 = latitude1 * Math.PI / 180; // φ, λ in radians const φ2 = latitude2 * Math.PI / 180; const Δφ = (latitude2 - latitude1) * Math.PI / 180; const Δλ = (longitude2 - longitude1) * Math.PI / 180; const a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) + Math.cos1) * Math.cos2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2); // square of half the chord length between the points using the Haversine formula. const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); // angular distance in radians. return R * c; // distance in meters }; exports._distance = _distance; /** * Get logarithm of value with custom base (i.e. `log_x * value = log * value/log * base`) * - power of base in value * * @param base - log base * @param value - log value * @returns `number` approximatted float */ const _logx = (base, value) => { if (isNaN(base = (0, exports._num)(base))) return NaN; if (isNaN(value = (0, exports._num)(value))) return NaN; return Math.log2(value) / Math.log2(base); }; exports._logx = _logx; /** * Get number in `k` (thousand)` to max `T` (trillion) SI Symbol value group * - `'k'` Thousand * - `'M'` Million * - `'B'` Billion * - `'T'` Trillion * * @param value - parse number * @param places - decimal places (0 - 3) * @returns `string` number text */ const _numk = (value, places = 1) => { if (isNaN(value = (0, exports._num)(value))) return NaN.toString(); if (!value) return '0'; places = (0, exports._posInt)(places, 0, 3, true) ?? 1; const k = 1e3, units = ['', 'k', 'M', 'B', 'T'], max_pow = units.length - 1; const n = value < 0 ? '-' : '', pow = Math.floor((0, exports._logx)(k, value = Math.abs(value))); const i = Math.min(pow, max_pow), unit = units[i]; let val = (0, exports._round)(value / (k ** i), places); let text = `${n}${val}${unit}`; if (pow > max_pow) { const e = Math.floor(Math.log10(val)); val = (0, exports._round)(val / (10 ** e), places); text = `${n}${val}e${e}${unit}`; } return text; }; exports._numk = _numk; /** * Parse integer value * * @param val - parse value * @returns `number` or `0` */ const _parse_int = (val, base, _default = 0) => { const res = parseInt(val, base); return isNaN(res) ? _default : (!res ? 0 : res); }; exports._parse_int = _parse_int; /** * Parse float value * * @param val - parse value * @returns `number` or `0` */ const _parse_float = (val, _default = 0) => { const res = parseFloat(val); return isNaN(res) ? _default : (!res ? 0 : res); }; exports._parse_float = _parse_float; /** * Clamp a number between min and max * * @param num - number to clamp * @param min - minimum value * @param max - maximum value * @returns `number` */ const _clamp = (num, min, max) => Math.min(Math.max((0, exports._num)(min), (0, exports._num)(num)), (0, exports._num)(max)); exports._clamp = _clamp; //# sourceMappingURL=_number.js.map