From cc41ddef0378990d0d6e5612917ef284bbdb820b Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Tue, 15 Jul 2025 11:04:04 +0200 Subject: [PATCH 01/36] initiate parse_int once --- strnum.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/strnum.js b/strnum.js index 330da88..e2960da 100644 --- a/strnum.js +++ b/strnum.js @@ -3,7 +3,6 @@ const numRegex = /^([\-\+])?(0*)([0-9]*(\.[0-9]*)?)$/; // const octRegex = /^0x[a-z0-9]+/; // const binRegex = /0x[a-z0-9]+/; - const consider = { hex : true, // oct: false, @@ -119,11 +118,12 @@ function trimZeros(numStr){ } return numStr; } - -function parse_int(numStr, base){ - //polyfill - if(parseInt) return parseInt(numStr, base); - else if(Number.parseInt) return Number.parseInt(numStr, base); - else if(window && window.parseInt) return window.parseInt(numStr, base); - else throw new Error("parseInt, Number.parseInt, window.parseInt are not supported") -} \ No newline at end of file + +const parse_int = /** @type {(string: string, radix: 16) => number} */ ((function parse_int(){ + if(parseInt) return parseInt + else if(Number.parseInt) return Number.parseInt + else if(window && window.parseInt) return window.parseInt + else return function parseInt() { + throw new Error("parseInt, Number.parseInt, window.parseInt are not supported") + }; +})()); From 653d28e0583da4bd4c9e5a41af449d7cce559ab7 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Tue, 15 Jul 2025 11:05:48 +0200 Subject: [PATCH 02/36] remove commented out code --- strnum.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/strnum.js b/strnum.js index e2960da..018295f 100644 --- a/strnum.js +++ b/strnum.js @@ -1,15 +1,11 @@ const hexRegex = /^[-+]?0x[a-fA-F0-9]+$/; const numRegex = /^([\-\+])?(0*)([0-9]*(\.[0-9]*)?)$/; -// const octRegex = /^0x[a-z0-9]+/; -// const binRegex = /0x[a-z0-9]+/; const consider = { hex : true, - // oct: false, leadingZeros: true, decimalPoint: "\.", eNotation: true, - //skipLike: /regex/ }; export default function toNumber(str, options = {}){ @@ -22,12 +18,8 @@ export default function toNumber(str, options = {}){ else if(str==="0") return 0; else if (options.hex && hexRegex.test(trimmedStr)) { return parse_int(trimmedStr, 16); - // }else if (options.oct && octRegex.test(str)) { - // return Number.parseInt(val, 8); }else if (trimmedStr.search(/.+[eE].+/)!== -1) { //eNotation return resolveEnotation(str,trimmedStr,options); - // }else if (options.parseBin && binRegex.test(str)) { - // return Number.parseInt(val, 2); }else{ //separate negative sign, leading zeros, and rest number const match = numRegex.exec(trimmedStr); From b6a172377603663484df043d361a2014bb5fd115 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Tue, 15 Jul 2025 11:44:18 +0200 Subject: [PATCH 03/36] add jsdoc --- strnum.js | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/strnum.js b/strnum.js index 018295f..1d32b27 100644 --- a/strnum.js +++ b/strnum.js @@ -1,15 +1,31 @@ const hexRegex = /^[-+]?0x[a-fA-F0-9]+$/; const numRegex = /^([\-\+])?(0*)([0-9]*(\.[0-9]*)?)$/; -const consider = { +/** + * @typedef {Object} Options + * @property {boolean} [hex=true] - Whether to allow hexadecimal numbers (e.g., "0x1A"). + * @property {boolean} [leadingZeros=true] - Whether to allow leading zeros in numbers. + * @property {RegExp} [skipLike] - A regular expression to skip certain string patterns. + * @property {string} [decimalPoint="."] - The character used as the decimal point. + * @property {boolean} [eNotation=true] - Whether to allow scientific notation (e.g., "1e10"). + */ + +/** @type {Options} */ +const defaultOptions = { hex : true, leadingZeros: true, decimalPoint: "\.", eNotation: true, }; +/** + * @template {*} T + * @param {T} str + * @param {Options} options + * @returns {number|T} + */ export default function toNumber(str, options = {}){ - options = Object.assign({}, consider, options ); + options = Object.assign({}, defaultOptions, options); if(!str || typeof str !== "string" ) return str; let trimmedStr = str.trim(); @@ -70,6 +86,14 @@ export default function toNumber(str, options = {}){ } const eNotationRegx = /^([-+])?(0*)(\d*(\.\d*)?[eE][-\+]?\d+)$/; + +/** + * @template {*} T + * @param {T} str + * @param {string} trimmedStr + * @param {Options} options + * @returns {number|T} + */ function resolveEnotation(str,trimmedStr,options){ if(!options.eNotation) return str; const notation = trimmedStr.match(eNotationRegx); @@ -96,9 +120,8 @@ function resolveEnotation(str,trimmedStr,options){ } /** - * * @param {string} numStr without leading zeros - * @returns + * @returns {string} numStr with trimmed ending zeros */ function trimZeros(numStr){ if(numStr && numStr.indexOf(".") !== -1){//float @@ -111,7 +134,10 @@ function trimZeros(numStr){ return numStr; } -const parse_int = /** @type {(string: string, radix: 16) => number} */ ((function parse_int(){ +/** + * @type {(string: string, radix: 16) => number} + */ +const parse_int = ((function parse_int(){ if(parseInt) return parseInt else if(Number.parseInt) return Number.parseInt else if(window && window.parseInt) return window.parseInt From 8ce60c662b8e927c78b3cbcb737e85622ea7dd79 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Tue, 15 Jul 2025 12:00:50 +0200 Subject: [PATCH 04/36] add benchmark based on tests --- benchmark.js | 147 ++++++++++++ package-lock.json | 567 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 3 +- strnum.test.js | 1 - 4 files changed, 716 insertions(+), 2 deletions(-) create mode 100644 benchmark.js create mode 100644 package-lock.json diff --git a/benchmark.js b/benchmark.js new file mode 100644 index 0000000..6dc4da3 --- /dev/null +++ b/benchmark.js @@ -0,0 +1,147 @@ +import { Bench } from 'tinybench' +import toNumber from "./strnum.js" + +const bench = new Bench({ name: 'strnum benchmark', time: 100 }) + +function toNumberBenchmark(str, options) { + bench.add(`${str}${options ? JSON.stringify(options) :''}`, () => { + toNumber(str, options) + }) +} + +toNumberBenchmark(undefined); +toNumberBenchmark(null); +toNumberBenchmark(""); +toNumberBenchmark("string"); +toNumberBenchmark("e89794659669cb7bb967db73a7ea6889c3891727") +toNumberBenchmark("12,12"); +toNumberBenchmark("12 12"); +toNumberBenchmark("12-12"); +toNumberBenchmark("12.12.12"); +toNumberBenchmark("+12"); +toNumberBenchmark("+ 12"); +toNumberBenchmark("12+12"); +toNumberBenchmark("1212+"); +toNumberBenchmark("0x2f"); +toNumberBenchmark("-0x2f"); +toNumberBenchmark("0x2f", { hex: true }); +toNumberBenchmark("-0x2f", { hex: true }); +toNumberBenchmark("0x2f", { hex: false }); +toNumberBenchmark("-0x2f", { hex: false }); +toNumberBenchmark("0xzz"); +toNumberBenchmark("iweraf0x123qwerqwer"); +toNumberBenchmark("1230x55"); +toNumberBenchmark("JVBERi0xLjMNCiXi48"); +toNumberBenchmark("0"); +toNumberBenchmark("00"); +toNumberBenchmark("00.0"); + +toNumberBenchmark("0", { leadingZeros: false }); +toNumberBenchmark("00", { leadingZeros: false }); +toNumberBenchmark("00.0", { leadingZeros: false }); + +toNumberBenchmark("06"); +toNumberBenchmark("06", { leadingZeros: true }); +toNumberBenchmark("06", { leadingZeros: false }); + +toNumberBenchmark("006"); +toNumberBenchmark("006", { leadingZeros: true }); +toNumberBenchmark("006", { leadingZeros: false }); + +toNumberBenchmark("000000000000000000000000017717", { leadingZeros: false }); +toNumberBenchmark("000000000000000000000000017717", { leadingZeros: true }); +toNumberBenchmark("0420926189200190257681175017717"); +toNumberBenchmark("20.21.030"); +toNumberBenchmark("0.21.030"); +toNumberBenchmark("0.21."); +toNumberBenchmark("0."); +toNumberBenchmark("+0."); +toNumberBenchmark("-0."); +toNumberBenchmark("1."); +toNumberBenchmark("00.00"); +toNumberBenchmark("0.06"); +toNumberBenchmark("00.6"); +toNumberBenchmark(".006"); +toNumberBenchmark("6.0"); +toNumberBenchmark("06.0"); + +toNumberBenchmark("0.0", { leadingZeros: false }); +toNumberBenchmark("00.00", { leadingZeros: false }); +toNumberBenchmark("0.06", { leadingZeros: false }); +toNumberBenchmark("00.6", { leadingZeros: false }); +toNumberBenchmark(".006", { leadingZeros: false }); +toNumberBenchmark("6.0", { leadingZeros: false }); +toNumberBenchmark("06.0", { leadingZeros: false }); +toNumberBenchmark("+06"); +toNumberBenchmark("-06"); +toNumberBenchmark("-06", { leadingZeros: true }); +toNumberBenchmark("-06", { leadingZeros: false }); + +toNumberBenchmark("-0.0"); +toNumberBenchmark("-00.00"); +toNumberBenchmark("-0.06"); +toNumberBenchmark("-00.6"); +toNumberBenchmark("-.006"); +toNumberBenchmark("-6.0"); +toNumberBenchmark("-06.0"); +toNumberBenchmark("+06.0"); + +toNumberBenchmark("-0.0", { leadingZeros: false }); +toNumberBenchmark("-00.00", { leadingZeros: false }); +toNumberBenchmark("-0.06", { leadingZeros: false }); +toNumberBenchmark("-00.6", { leadingZeros: false }); +toNumberBenchmark("-.006", { leadingZeros: false }); +toNumberBenchmark("-6.0", { leadingZeros: false }); +toNumberBenchmark("-06.0", { leadingZeros: false }); +toNumberBenchmark("020211201030005811824"); +toNumberBenchmark("20211201030005811824"); +toNumberBenchmark("20.211201030005811824"); +toNumberBenchmark("0.211201030005811824"); +toNumberBenchmark("01.0e2", { leadingZeros: false }); +toNumberBenchmark("-01.0e2", { leadingZeros: false }); +toNumberBenchmark("01.0e2"); +toNumberBenchmark("-01.0e2"); +toNumberBenchmark("1.0e2"); + +toNumberBenchmark("-1.0e2"); +toNumberBenchmark("1.0e-2"); + +toNumberBenchmark("420926189200190257681175017717"); +toNumberBenchmark("420926189200190257681175017717", { eNotation: false }); + +toNumberBenchmark("1e-2"); +toNumberBenchmark("1e+2"); +toNumberBenchmark("1.e+2"); +toNumberBenchmark("01.0E2", { leadingZeros: false }); +toNumberBenchmark("-01.0E2", { leadingZeros: false }); +toNumberBenchmark("01.0E2"); +toNumberBenchmark("-01.0E2"); +toNumberBenchmark("1.0E2"); + +toNumberBenchmark("-1.0E2"); +toNumberBenchmark("1.0E-2"); + +toNumberBenchmark("E-2"); +toNumberBenchmark("E2"); +toNumberBenchmark("0E2"); +toNumberBenchmark("-0E2"); +toNumberBenchmark("00E2"); +toNumberBenchmark("00E2", { leadingZeros: false }); +toNumberBenchmark("0", { skipLike: /.*/ }); +toNumberBenchmark("+12", { skipLike: /\+[0-9]{10}/ }); +toNumberBenchmark("12+12", { skipLike: /\+[0-9]{10}/ }); +toNumberBenchmark("12+1212121212", { skipLike: /\+[0-9]{10}/ }); +toNumberBenchmark("+1212121212"); +toNumberBenchmark("+1212121212", { skipLike: /\+[0-9]{10}/ }); +toNumberBenchmark("+12 12"); +toNumberBenchmark(" +12 12 "); +toNumberBenchmark(" +1212 "); +toNumberBenchmark("+1212"); +toNumberBenchmark("+12.12"); +toNumberBenchmark("-12.12"); +toNumberBenchmark("-012.12"); + +await bench.run() + +console.log(bench.name) +console.table(bench.table()) \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..6e84ec3 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,567 @@ +{ + "name": "strnum", + "version": "2.1.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "strnum", + "version": "2.1.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "devDependencies": { + "jasmine": "^5.6.0", + "tinybench": "^4.0.1" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jasmine": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-5.8.0.tgz", + "integrity": "sha512-1V6HGa0+TMoMY20+/vp++RqLlL1noupV8awzV6CiPuICC0g7iKZ9z87zV2KyelRyoig0G1lHn7ueElXVMGVagg==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "^10.2.2", + "jasmine-core": "~5.8.0" + }, + "bin": { + "jasmine": "bin/jasmine.js" + } + }, + "node_modules/jasmine-core": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.8.0.tgz", + "integrity": "sha512-Q9dqmpUAfptwyueW3+HqBOkSuYd9I/clZSSfN97wXE/Nr2ROFNCwIBEC1F6kb3QXS9Fcz0LjFYSDQT+BiwjuhA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/tinybench": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-4.0.1.tgz", + "integrity": "sha512-Nb1srn7dvzkVx0J5h1vq8f48e3TIcbrS7e/UfAI/cDSef/n8yLh4zsAEsFkfpw6auTY+ZaspEvam/xs8nMnotQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + } + } +} diff --git a/package.json b/package.json index b60e835..65b6bac 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ } ], "devDependencies": { - "jasmine": "^5.6.0" + "jasmine": "^5.6.0", + "tinybench": "^4.0.1" } } diff --git a/strnum.test.js b/strnum.test.js index 2c44fdb..f333801 100644 --- a/strnum.test.js +++ b/strnum.test.js @@ -168,6 +168,5 @@ describe("Should convert all the valid numeric strings to number", () => { expect(toNumber("+12.12")).toEqual(12.12); expect(toNumber("-12.12")).toEqual(-12.12); expect(toNumber("-012.12")).toEqual(-12.12); - expect(toNumber("-012.12")).toEqual(-12.12); }) }); From 4bd6dd14bac4ab337298176346bc32ac99f09147 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Tue, 15 Jul 2025 12:20:22 +0200 Subject: [PATCH 05/36] format with vscode --- strnum.js | 108 +++++++++++++++++++++++++++--------------------------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/strnum.js b/strnum.js index 1d32b27..4448cdf 100644 --- a/strnum.js +++ b/strnum.js @@ -12,7 +12,7 @@ const numRegex = /^([\-\+])?(0*)([0-9]*(\.[0-9]*)?)$/; /** @type {Options} */ const defaultOptions = { - hex : true, + hex: true, leadingZeros: true, decimalPoint: "\.", eNotation: true, @@ -24,62 +24,62 @@ const defaultOptions = { * @param {Options} options * @returns {number|T} */ -export default function toNumber(str, options = {}){ +export default function toNumber(str, options = {}) { options = Object.assign({}, defaultOptions, options); - if(!str || typeof str !== "string" ) return str; - - let trimmedStr = str.trim(); - - if(options.skipLike !== undefined && options.skipLike.test(trimmedStr)) return str; - else if(str==="0") return 0; + if (!str || typeof str !== "string") return str; + + let trimmedStr = str.trim(); + + if (options.skipLike !== undefined && options.skipLike.test(trimmedStr)) return str; + else if (str === "0") return 0; else if (options.hex && hexRegex.test(trimmedStr)) { return parse_int(trimmedStr, 16); - }else if (trimmedStr.search(/.+[eE].+/)!== -1) { //eNotation - return resolveEnotation(str,trimmedStr,options); - }else{ + } else if (trimmedStr.search(/.+[eE].+/) !== -1) { //eNotation + return resolveEnotation(str, trimmedStr, options); + } else { //separate negative sign, leading zeros, and rest number const match = numRegex.exec(trimmedStr); // +00.123 => [ , '+', '00', '.123', .. - if(match){ + if (match) { const sign = match[1] || ""; const leadingZeros = match[2]; let numTrimmedByZeros = trimZeros(match[3]); //complete num without leading zeros const decimalAdjacentToLeadingZeros = sign ? // 0., -00., 000. - str[leadingZeros.length+1] === "." + str[leadingZeros.length + 1] === "." : str[leadingZeros.length] === "."; //trim ending zeros for floating number - if(!options.leadingZeros //leading zeros are not allowed - && (leadingZeros.length > 1 - || (leadingZeros.length === 1 && !decimalAdjacentToLeadingZeros))){ + if (!options.leadingZeros //leading zeros are not allowed + && (leadingZeros.length > 1 + || (leadingZeros.length === 1 && !decimalAdjacentToLeadingZeros))) { // 00, 00.3, +03.24, 03, 03.24 return str; } - else{//no leading zeros or leading zeros are allowed + else {//no leading zeros or leading zeros are allowed const num = Number(trimmedStr); const parsedStr = String(num); - if( num === 0) return num; - if(parsedStr.search(/[eE]/) !== -1){ //given number is long and parsed to eNotation - if(options.eNotation) return num; + if (num === 0) return num; + if (parsedStr.search(/[eE]/) !== -1) { //given number is long and parsed to eNotation + if (options.eNotation) return num; else return str; - }else if(trimmedStr.indexOf(".") !== -1){ //floating number - if(parsedStr === "0") return num; //0.0 - else if(parsedStr === numTrimmedByZeros) return num; //0.456. 0.79000 - else if( parsedStr === `${sign}${numTrimmedByZeros}`) return num; + } else if (trimmedStr.indexOf(".") !== -1) { //floating number + if (parsedStr === "0") return num; //0.0 + else if (parsedStr === numTrimmedByZeros) return num; //0.456. 0.79000 + else if (parsedStr === `${sign}${numTrimmedByZeros}`) return num; else return str; } - - let n = leadingZeros? numTrimmedByZeros : trimmedStr; - if(leadingZeros){ + + let n = leadingZeros ? numTrimmedByZeros : trimmedStr; + if (leadingZeros) { // -009 => -9 - return (n === parsedStr) || (sign+n === parsedStr) ? num : str - }else { + return (n === parsedStr) || (sign + n === parsedStr) ? num : str + } else { // +9 - return (n === parsedStr) || (n === sign+parsedStr) ? num : str + return (n === parsedStr) || (n === sign + parsedStr) ? num : str } } - }else{ //non-numeric string + } else { //non-numeric string return str; } } @@ -94,27 +94,27 @@ const eNotationRegx = /^([-+])?(0*)(\d*(\.\d*)?[eE][-\+]?\d+)$/; * @param {Options} options * @returns {number|T} */ -function resolveEnotation(str,trimmedStr,options){ - if(!options.eNotation) return str; - const notation = trimmedStr.match(eNotationRegx); - if(notation){ +function resolveEnotation(str, trimmedStr, options) { + if (!options.eNotation) return str; + const notation = trimmedStr.match(eNotationRegx); + if (notation) { let sign = notation[1] || ""; const eChar = notation[3].indexOf("e") === -1 ? "E" : "e"; const leadingZeros = notation[2]; const eAdjacentToLeadingZeros = sign ? // 0E. - str[leadingZeros.length+1] === eChar + str[leadingZeros.length + 1] === eChar : str[leadingZeros.length] === eChar; - if(leadingZeros.length > 1 && eAdjacentToLeadingZeros) return str; - else if(leadingZeros.length === 1 - && (notation[3].startsWith(`.${eChar}`) || notation[3][0] === eChar)){ - return Number(trimmedStr); - }else if(options.leadingZeros && !eAdjacentToLeadingZeros){ //accept with leading zeros + if (leadingZeros.length > 1 && eAdjacentToLeadingZeros) return str; + else if (leadingZeros.length === 1 + && (notation[3].startsWith(`.${eChar}`) || notation[3][0] === eChar)) { + return Number(trimmedStr); + } else if (options.leadingZeros && !eAdjacentToLeadingZeros) { //accept with leading zeros //remove leading 0s trimmedStr = (notation[1] || "") + notation[3]; return Number(trimmedStr); - }else return str; - }else{ + } else return str; + } else { return str; } } @@ -123,25 +123,25 @@ function resolveEnotation(str,trimmedStr,options){ * @param {string} numStr without leading zeros * @returns {string} numStr with trimmed ending zeros */ -function trimZeros(numStr){ - if(numStr && numStr.indexOf(".") !== -1){//float +function trimZeros(numStr) { + if (numStr && numStr.indexOf(".") !== -1) {//float numStr = numStr.replace(/0+$/, ""); //remove ending zeros - if(numStr === ".") numStr = "0"; - else if(numStr[0] === ".") numStr = "0"+numStr; - else if(numStr[numStr.length-1] === ".") numStr = numStr.substring(0,numStr.length-1); + if (numStr === ".") numStr = "0"; + else if (numStr[0] === ".") numStr = "0" + numStr; + else if (numStr[numStr.length - 1] === ".") numStr = numStr.substring(0, numStr.length - 1); return numStr; } return numStr; } - + /** * @type {(string: string, radix: 16) => number} */ -const parse_int = ((function parse_int(){ - if(parseInt) return parseInt - else if(Number.parseInt) return Number.parseInt - else if(window && window.parseInt) return window.parseInt - else return function parseInt() { +const parse_int = ((function parse_int() { + if (parseInt) return parseInt + else if (Number.parseInt) return Number.parseInt + else if (window && window.parseInt) return window.parseInt + else return function parseInt() { throw new Error("parseInt, Number.parseInt, window.parseInt are not supported") }; })()); From 143e930f37d7513957fdf266e4098cc0bec3ef20 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Tue, 15 Jul 2025 12:23:38 +0200 Subject: [PATCH 06/36] remove enotation check in resolveEnotation --- strnum.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/strnum.js b/strnum.js index 4448cdf..aec2f50 100644 --- a/strnum.js +++ b/strnum.js @@ -35,6 +35,7 @@ export default function toNumber(str, options = {}) { else if (options.hex && hexRegex.test(trimmedStr)) { return parse_int(trimmedStr, 16); } else if (trimmedStr.search(/.+[eE].+/) !== -1) { //eNotation + if (options.eNotation === false) return str; //skip eNotation{ return resolveEnotation(str, trimmedStr, options); } else { //separate negative sign, leading zeros, and rest number @@ -91,11 +92,11 @@ const eNotationRegx = /^([-+])?(0*)(\d*(\.\d*)?[eE][-\+]?\d+)$/; * @template {*} T * @param {T} str * @param {string} trimmedStr - * @param {Options} options + * @param {object} options + * @param {boolean} [options.leadingZeros=true] * @returns {number|T} */ function resolveEnotation(str, trimmedStr, options) { - if (!options.eNotation) return str; const notation = trimmedStr.match(eNotationRegx); if (notation) { let sign = notation[1] || ""; @@ -124,7 +125,7 @@ function resolveEnotation(str, trimmedStr, options) { * @returns {string} numStr with trimmed ending zeros */ function trimZeros(numStr) { - if (numStr && numStr.indexOf(".") !== -1) {//float + if (numStr.indexOf(".") !== -1) {//float numStr = numStr.replace(/0+$/, ""); //remove ending zeros if (numStr === ".") numStr = "0"; else if (numStr[0] === ".") numStr = "0" + numStr; From a830509c1aeaf9d7bbda22d97279db1b62f21f15 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Tue, 15 Jul 2025 13:47:16 +0200 Subject: [PATCH 07/36] improve jsdoc desc --- bench.orig.txt | 124 +++++++++++++++++++++++++++++++++++++++++++++++++ strnum.js | 6 +-- 2 files changed, 127 insertions(+), 3 deletions(-) create mode 100644 bench.orig.txt diff --git a/bench.orig.txt b/bench.orig.txt new file mode 100644 index 0000000..7c3e2ee --- /dev/null +++ b/bench.orig.txt @@ -0,0 +1,124 @@ +strnum benchmark +┌─────────┬────────────────────────────────────────────────────────┬───────────────────┬──────────────────┬────────────────────────┬────────────────────────┬─────────┐ +│ (index) │ Task name │ Latency avg (ns) │ Latency med (ns) │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │ +├─────────┼────────────────────────────────────────────────────────┼───────────────────┼──────────────────┼────────────────────────┼────────────────────────┼─────────┤ +│ 0 │ 'undefined' │ '176.28 ± 0.75%' │ '156.00 ± 2.00' │ '6271038 ± 0.02%' │ '6410256 ± 83250' │ 567292 │ +│ 1 │ 'null' │ '188.77 ± 9.86%' │ '157.00 ± 3.00' │ '6197657 ± 0.02%' │ '6369427 ± 119427' │ 529734 │ +│ 2 │ '' │ '175.96 ± 0.70%' │ '156.00 ± 2.00' │ '6238677 ± 0.03%' │ '6410257 ± 83250' │ 568354 │ +│ 3 │ 'string' │ '401.63 ± 2.70%' │ '339.00 ± 8.00' │ '2872819 ± 0.04%' │ '2949853 ± 71296' │ 248987 │ +│ 4 │ 'e89794659669cb7bb967db73a7ea6889c3891727' │ '469.05 ± 1.54%' │ '398.00 ± 7.00' │ '2437276 ± 0.04%' │ '2512563 ± 44982' │ 213196 │ +│ 5 │ '12,12' │ '374.09 ± 1.62%' │ '331.00 ± 4.00' │ '2964088 ± 0.03%' │ '3021148 ± 36956' │ 267317 │ +│ 6 │ '12 12' │ '386.05 ± 1.67%' │ '328.00 ± 6.00' │ '2947028 ± 0.04%' │ '3048781 ± 54769' │ 259055 │ +│ 7 │ '12-12' │ '412.18 ± 16.46%' │ '328.00 ± 5.00' │ '2975623 ± 0.03%' │ '3048780 ± 47195' │ 242613 │ +│ 8 │ '12.12.12' │ '754.10 ± 2.23%' │ '794.00 ± 40.00' │ '1452149 ± 0.16%' │ '1259446 ± 60405' │ 132610 │ +│ 9 │ '+12' │ '577.84 ± 1.38%' │ '530.00 ± 5.00' │ '1842993 ± 0.04%' │ '1886792 ± 17969' │ 173060 │ +│ 10 │ '+ 12' │ '357.10 ± 1.47%' │ '321.00 ± 4.00' │ '3054220 ± 0.03%' │ '3115265 ± 39309' │ 280036 │ +│ 11 │ '12+12' │ '396.32 ± 1.61%' │ '331.00 ± 6.00' │ '2883455 ± 0.05%' │ '3021148 ± 55775' │ 252323 │ +│ 12 │ '1212+' │ '389.35 ± 1.55%' │ '340.00 ± 6.00' │ '2858365 ± 0.04%' │ '2941176 ± 52836' │ 256837 │ +│ 13 │ '0x2f' │ '260.14 ± 0.55%' │ '242.00 ± 3.00' │ '4062944 ± 0.02%' │ '4132231 ± 50599' │ 384403 │ +│ 14 │ '-0x2f' │ '296.43 ± 16.57%' │ '246.00 ± 3.00' │ '3982511 ± 0.03%' │ '4065041 ± 50186' │ 337354 │ +│ 15 │ '0x2f{"hex":true}' │ '273.09 ± 0.50%' │ '258.00 ± 3.00' │ '3830156 ± 0.02%' │ '3875969 ± 45600' │ 366184 │ +│ 16 │ '-0x2f{"hex":true}' │ '278.65 ± 0.61%' │ '257.00 ± 3.00' │ '3829122 ± 0.02%' │ '3891051 ± 45957' │ 358876 │ +│ 17 │ '0x2f{"hex":false}' │ '337.83 ± 0.70%' │ '305.00 ± 4.00' │ '3218980 ± 0.02%' │ '3278689 ± 43571' │ 296005 │ +│ 18 │ '-0x2f{"hex":false}' │ '401.92 ± 0.76%' │ '342.00 ± 9.00' │ '2791745 ± 0.05%' │ '2923977 ± 79026' │ 248807 │ +│ 19 │ '0xzz' │ '360.92 ± 2.76%' │ '322.00 ± 4.00' │ '3047684 ± 0.03%' │ '3105590 ± 39064' │ 277070 │ +│ 20 │ 'iweraf0x123qwerqwer' │ '352.81 ± 2.37%' │ '315.00 ± 5.00' │ '3112495 ± 0.03%' │ '3174603 ± 49603' │ 283579 │ +│ 21 │ '1230x55' │ '442.30 ± 1.67%' │ '377.00 ± 9.00' │ '2568964 ± 0.05%' │ '2652520 ± 61846' │ 226093 │ +│ 22 │ 'JVBERi0xLjMNCiXi48' │ '419.78 ± 1.82%' │ '356.00 ± 8.00' │ '2723575 ± 0.04%' │ '2808989 ± 61736' │ 238222 │ +│ 23 │ '0' │ '197.89 ± 0.72%' │ '174.00 ± 3.00' │ '5584882 ± 0.03%' │ '5747126 ± 100827' │ 505333 │ +│ 24 │ '00' │ '423.53 ± 1.44%' │ '382.00 ± 5.00' │ '2561154 ± 0.03%' │ '2617801 ± 33822' │ 236112 │ +│ 25 │ '00.0' │ '595.90 ± 0.71%' │ '532.00 ± 7.00' │ '1830700 ± 0.04%' │ '1879699 ± 25063' │ 167813 │ +│ 26 │ '0{"leadingZeros":false}' │ '211.07 ± 0.68%' │ '191.00 ± 3.00' │ '5140805 ± 0.02%' │ '5235602 ± 83547' │ 473773 │ +│ 27 │ '00{"leadingZeros":false}' │ '424.16 ± 3.16%' │ '360.00 ± 5.00' │ '2716280 ± 0.03%' │ '2777778 ± 39124' │ 235761 │ +│ 28 │ '00.0{"leadingZeros":false}' │ '555.64 ± 0.70%' │ '498.00 ± 6.00' │ '1959999 ± 0.04%' │ '2008032 ± 24488' │ 179974 │ +│ 29 │ '06' │ '500.89 ± 1.79%' │ '454.00 ± 4.00' │ '2161924 ± 0.03%' │ '2202643 ± 19579' │ 199646 │ +│ 30 │ '06{"leadingZeros":true}' │ '532.38 ± 2.86%' │ '468.00 ± 6.00' │ '2087822 ± 0.04%' │ '2136752 ± 27048' │ 187836 │ +│ 31 │ '06{"leadingZeros":false}' │ '447.79 ± 2.50%' │ '389.00 ± 5.00' │ '2522762 ± 0.03%' │ '2570694 ± 33473' │ 223319 │ +│ 32 │ '006' │ '612.60 ± 27.68%' │ '468.00 ± 5.00' │ '2090950 ± 0.04%' │ '2136752 ± 22587' │ 175858 │ +│ 33 │ '006{"leadingZeros":true}' │ '545.63 ± 9.20%' │ '480.00 ± 5.00' │ '2046745 ± 0.03%' │ '2083333 ± 21478' │ 183276 │ +│ 34 │ '006{"leadingZeros":false}' │ '447.74 ± 9.36%' │ '402.00 ± 5.00' │ '2448348 ± 0.03%' │ '2487562 ± 31329' │ 223344 │ +│ 35 │ '000000000000000000000000017717{"leadingZeros":false}' │ '1800.8 ± 2.15%' │ '1652.0 ± 9.00' │ '597184 ± 0.07%' │ '605327 ± 3316' │ 55530 │ +│ 36 │ '000000000000000000000000017717{"leadingZeros":true}' │ '1967.2 ± 1.03%' │ '1801.0 ± 9.00' │ '545915 ± 0.08%' │ '555247 ± 2761' │ 50836 │ +│ 37 │ '0420926189200190257681175017717' │ '2244.9 ± 4.93%' │ '1993.0 ± 13.00' │ '494292 ± 0.09%' │ '501756 ± 3294' │ 44545 │ +│ 38 │ '20.21.030' │ '477.83 ± 2.10%' │ '427.00 ± 5.00' │ '2306784 ± 0.03%' │ '2341920 ± 27748' │ 209280 │ +│ 39 │ '0.21.030' │ '479.55 ± 1.35%' │ '427.00 ± 6.00' │ '2300233 ± 0.03%' │ '2341920 ± 33377' │ 208527 │ +│ 40 │ '0.21.' │ '401.30 ± 1.58%' │ '372.00 ± 5.00' │ '2644391 ± 0.03%' │ '2688172 ± 35652' │ 249192 │ +│ 41 │ '0.' │ '520.13 ± 2.69%' │ '475.00 ± 6.00' │ '2054102 ± 0.03%' │ '2105263 ± 26933' │ 192260 │ +│ 42 │ '+0.' │ '549.97 ± 1.58%' │ '497.00 ± 7.00' │ '1961545 ± 0.03%' │ '2012072 ± 28744' │ 181829 │ +│ 43 │ '-0.' │ '589.83 ± 2.36%' │ '518.00 ± 8.00' │ '1887212 ± 0.04%' │ '1930502 ± 30282' │ 169540 │ +│ 44 │ '1.' │ '615.35 ± 2.23%' │ '551.00 ± 6.00' │ '1773254 ± 0.04%' │ '1814882 ± 19980' │ 162510 │ +│ 45 │ '00.00' │ '640.34 ± 8.32%' │ '553.00 ± 7.00' │ '1768126 ± 0.04%' │ '1808318 ± 23184' │ 156168 │ +│ 46 │ '0.06' │ '702.87 ± 5.95%' │ '620.00 ± 10.00' │ '1576319 ± 0.04%' │ '1612903 ± 25602' │ 142275 │ +│ 47 │ '00.6' │ '707.26 ± 2.64%' │ '619.00 ± 11.00' │ '1576901 ± 0.04%' │ '1615509 ± 29228' │ 141390 │ +│ 48 │ '.006' │ '698.30 ± 2.20%' │ '615.00 ± 13.00' │ '1590474 ± 0.05%' │ '1626016 ± 35113' │ 143205 │ +│ 49 │ '6.0' │ '650.21 ± 1.17%' │ '593.00 ± 12.00' │ '1646076 ± 0.04%' │ '1686341 ± 33448' │ 153798 │ +│ 50 │ '06.0' │ '720.14 ± 3.88%' │ '631.00 ± 14.00' │ '1551427 ± 0.04%' │ '1584786 ± 35960' │ 138862 │ +│ 51 │ '0.0{"leadingZeros":false}' │ '590.67 ± 1.67%' │ '531.00 ± 6.00' │ '1841661 ± 0.04%' │ '1883239 ± 21523' │ 169301 │ +│ 52 │ '00.00{"leadingZeros":false}' │ '550.61 ± 1.83%' │ '514.00 ± 5.00' │ '1913232 ± 0.03%' │ '1945525 ± 18743' │ 181618 │ +│ 53 │ '0.06{"leadingZeros":false}' │ '694.58 ± 0.63%' │ '635.00 ± 10.00' │ '1541587 ± 0.04%' │ '1574803 ± 25197' │ 143972 │ +│ 54 │ '00.6{"leadingZeros":false}' │ '527.42 ± 1.71%' │ '484.00 ± 5.00' │ '2025904 ± 0.03%' │ '2066116 ± 21567' │ 189603 │ +│ 55 │ '.006{"leadingZeros":false}' │ '698.97 ± 1.89%' │ '619.00 ± 8.00' │ '1575767 ± 0.04%' │ '1615509 ± 20613' │ 143851 │ +│ 56 │ '6.0{"leadingZeros":false}' │ '654.77 ± 1.51%' │ '606.00 ± 10.00' │ '1613908 ± 0.04%' │ '1650165 ± 26788' │ 152725 │ +│ 57 │ '06.0{"leadingZeros":false}' │ '588.63 ± 9.69%' │ '519.00 ± 5.00' │ '1893840 ± 0.03%' │ '1926782 ± 18743' │ 169885 │ +│ 58 │ '+06' │ '549.03 ± 1.86%' │ '500.00 ± 8.00' │ '1950534 ± 0.03%' │ '2000000 ± 32520' │ 182141 │ +│ 59 │ '-06' │ '547.42 ± 2.54%' │ '495.00 ± 6.00' │ '1975987 ± 0.04%' │ '2020202 ± 24788' │ 182676 │ +│ 60 │ '-06{"leadingZeros":true}' │ '552.08 ± 1.98%' │ '503.00 ± 5.00' │ '1949603 ± 0.03%' │ '1988072 ± 19568' │ 181135 │ +│ 61 │ '-06{"leadingZeros":false}' │ '460.01 ± 10.73%' │ '399.00 ± 4.00' │ '2466025 ± 0.03%' │ '2506266 ± 25380' │ 217389 │ +│ 62 │ '-0.0' │ '692.25 ± 14.45%' │ '561.00 ± 10.00' │ '1734246 ± 0.05%' │ '1782531 ± 32351' │ 144458 │ +│ 63 │ '-00.00' │ '638.14 ± 0.52%' │ '596.00 ± 6.00' │ '1643582 ± 0.03%' │ '1677852 ± 17063' │ 156705 │ +│ 64 │ '-0.06' │ '757.29 ± 4.69%' │ '674.00 ± 12.00' │ '1451495 ± 0.04%' │ '1483680 ± 25954' │ 132051 │ +│ 65 │ '-00.6' │ '932.64 ± 2.03%' │ '724.00 ± 41.00' │ '1205406 ± 0.14%' │ '1381215 ± 82913' │ 107223 │ +│ 66 │ '-.006' │ '750.61 ± 2.10%' │ '681.00 ± 13.00' │ '1447778 ± 0.04%' │ '1468429 ± 28577' │ 133226 │ +│ 67 │ '-6.0' │ '729.81 ± 0.68%' │ '651.00 ± 10.00' │ '1488765 ± 0.05%' │ '1536098 ± 23964' │ 137023 │ +│ 68 │ '-06.0' │ '774.08 ± 7.77%' │ '672.00 ± 10.00' │ '1453810 ± 0.05%' │ '1488095 ± 21820' │ 129185 │ +│ 69 │ '+06.0' │ '735.38 ± 2.33%' │ '642.00 ± 10.00' │ '1514921 ± 0.05%' │ '1557632 ± 24646' │ 135984 │ +│ 70 │ '-0.0{"leadingZeros":false}' │ '638.38 ± 1.26%' │ '571.00 ± 6.00' │ '1702628 ± 0.05%' │ '1751313 ± 18598' │ 156646 │ +│ 71 │ '-00.00{"leadingZeros":false}' │ '627.92 ± 3.64%' │ '541.00 ± 5.00' │ '1810988 ± 0.04%' │ '1848429 ± 17243' │ 159256 │ +│ 72 │ '-0.06{"leadingZeros":false}' │ '747.75 ± 2.00%' │ '679.00 ± 7.00' │ '1443515 ± 0.04%' │ '1472754 ± 15341' │ 133735 │ +│ 73 │ '-00.6{"leadingZeros":false}' │ '568.70 ± 1.62%' │ '512.00 ± 5.00' │ '1915025 ± 0.03%' │ '1953125 ± 19262' │ 175840 │ +│ 74 │ '-.006{"leadingZeros":false}' │ '850.33 ± 2.64%' │ '684.00 ± 11.00' │ '1361597 ± 0.10%' │ '1461988 ± 23139' │ 117602 │ +│ 75 │ '-6.0{"leadingZeros":false}' │ '760.93 ± 3.57%' │ '661.00 ± 9.00' │ '1477477 ± 0.05%' │ '1512859 ± 20883' │ 131419 │ +│ 76 │ '-06.0{"leadingZeros":false}' │ '595.55 ± 4.24%' │ '538.00 ± 5.00' │ '1826741 ± 0.03%' │ '1858736 ± 17437' │ 167914 │ +│ 77 │ '020211201030005811824' │ '1345.6 ± 1.29%' │ '1254.0 ± 11.00' │ '786619 ± 0.05%' │ '797448 ± 7057' │ 74316 │ +│ 78 │ '20211201030005811824' │ '1224.5 ± 1.48%' │ '1118.0 ± 11.00' │ '878751 ± 0.06%' │ '894454 ± 8888' │ 81665 │ +│ 79 │ '20.211201030005811824' │ '1403.1 ± 1.47%' │ '1267.0 ± 14.00' │ '771738 ± 0.07%' │ '789266 ± 8819' │ 71272 │ +│ 80 │ '0.211201030005811824' │ '1375.4 ± 4.39%' │ '1232.0 ± 21.00' │ '797888 ± 0.06%' │ '811688 ± 14076' │ 72707 │ +│ 81 │ '01.0e2{"leadingZeros":false}' │ '509.33 ± 2.99%' │ '426.00 ± 5.00' │ '2213375 ± 0.07%' │ '2347418 ± 27879' │ 196337 │ +│ 82 │ '-01.0e2{"leadingZeros":false}' │ '506.44 ± 7.77%' │ '439.00 ± 4.00' │ '2231913 ± 0.03%' │ '2277904 ± 20946' │ 197456 │ +│ 83 │ '01.0e2' │ '566.50 ± 3.40%' │ '490.00 ± 5.00' │ '1997240 ± 0.04%' │ '2040816 ± 21039' │ 176522 │ +│ 84 │ '-01.0e2' │ '585.88 ± 2.57%' │ '511.00 ± 5.00' │ '1913301 ± 0.04%' │ '1956947 ± 19337' │ 170682 │ +│ 85 │ '1.0e2' │ '502.10 ± 2.12%' │ '441.00 ± 4.00' │ '2221158 ± 0.03%' │ '2267574 ± 20756' │ 199270 │ +│ 86 │ '-1.0e2' │ '557.07 ± 3.24%' │ '489.00 ± 4.00' │ '2002467 ± 0.03%' │ '2044990 ± 16866' │ 179511 │ +│ 87 │ '1.0e-2' │ '507.55 ± 1.65%' │ '458.00 ± 5.00' │ '2139581 ± 0.03%' │ '2183406 ± 24099' │ 197025 │ +│ 88 │ '420926189200190257681175017717' │ '2003.1 ± 1.14%' │ '1852.0 ± 11.00' │ '531529 ± 0.08%' │ '539957 ± 3226' │ 49923 │ +│ 89 │ '420926189200190257681175017717{"eNotation":false}' │ '2252.9 ± 1.38%' │ '1885.0 ± 21.00' │ '495379 ± 0.16%' │ '530504 ± 5845' │ 44387 │ +│ 90 │ '1e-2' │ '531.57 ± 4.00%' │ '435.00 ± 6.00' │ '2167114 ± 0.07%' │ '2298851 ± 32152' │ 188126 │ +│ 91 │ '1e+2' │ '487.80 ± 10.17%' │ '422.00 ± 4.00' │ '2326420 ± 0.03%' │ '2369668 ± 22676' │ 205002 │ +│ 92 │ '1.e+2' │ '495.84 ± 3.41%' │ '441.00 ± 4.00' │ '2216706 ± 0.04%' │ '2267574 ± 20383' │ 201678 │ +│ 93 │ '01.0E2{"leadingZeros":false}' │ '474.01 ± 1.21%' │ '426.00 ± 5.00' │ '2264958 ± 0.05%' │ '2347418 ± 27232' │ 210968 │ +│ 94 │ '-01.0E2{"leadingZeros":false}' │ '479.89 ± 0.63%' │ '440.00 ± 4.00' │ '2232908 ± 0.03%' │ '2272727 ± 20851' │ 208380 │ +│ 95 │ '01.0E2' │ '563.06 ± 3.63%' │ '490.00 ± 4.00' │ '2003573 ± 0.03%' │ '2040816 ± 16797' │ 177602 │ +│ 96 │ '-01.0E2' │ '591.72 ± 6.69%' │ '509.00 ± 5.00' │ '1913905 ± 0.04%' │ '1964637 ± 19490' │ 169023 │ +│ 97 │ '1.0E2' │ '488.81 ± 1.93%' │ '438.00 ± 4.00' │ '2227179 ± 0.04%' │ '2283105 ± 21042' │ 204580 │ +│ 98 │ '-1.0E2' │ '574.41 ± 2.99%' │ '486.00 ± 6.00' │ '1994244 ± 0.05%' │ '2057613 ± 25093' │ 174093 │ +│ 99 │ '1.0E-2' │ '514.82 ± 2.64%' │ '457.00 ± 6.00' │ '2136942 ± 0.04%' │ '2188184 ± 29111' │ 194242 │ +│ 100 │ 'E-2' │ '337.80 ± 3.29%' │ '291.00 ± 4.00' │ '3368998 ± 0.03%' │ '3436426 ± 46596' │ 296030 │ +│ 101 │ 'E2' │ '342.97 ± 4.02%' │ '284.00 ± 4.00' │ '3436589 ± 0.03%' │ '3521127 ± 50302' │ 292899 │ +│ 102 │ '0E2' │ '531.56 ± 2.58%' │ '437.00 ± 6.00' │ '2154611 ± 0.07%' │ '2288330 ± 31856' │ 188128 │ +│ 103 │ '-0E2' │ '525.57 ± 3.34%' │ '463.00 ± 5.00' │ '2112493 ± 0.03%' │ '2159827 ± 23579' │ 190269 │ +│ 104 │ '00E2' │ '428.53 ± 2.14%' │ '380.00 ± 5.00' │ '2558436 ± 0.04%' │ '2631579 ± 35088' │ 233356 │ +│ 105 │ '00E2{"leadingZeros":false}' │ '446.23 ± 3.37%' │ '391.00 ± 4.00' │ '2507832 ± 0.03%' │ '2557545 ± 26435' │ 224101 │ +│ 106 │ '0{"skipLike":{}}' │ '275.63 ± 4.39%' │ '230.00 ± 3.00' │ '4263003 ± 0.02%' │ '4347826 ± 55981' │ 362807 │ +│ 107 │ '+12{"skipLike":{}}' │ '796.27 ± 26.20%' │ '566.00 ± 8.00' │ '1722058 ± 0.05%' │ '1766784 ± 25330' │ 125586 │ +│ 108 │ '12+12{"skipLike":{}}' │ '432.11 ± 2.99%' │ '379.00 ± 5.00' │ '2588268 ± 0.03%' │ '2638522 ± 35274' │ 231421 │ +│ 109 │ '12+1212121212{"skipLike":{}}' │ '295.40 ± 4.34%' │ '253.00 ± 3.00' │ '3895165 ± 0.02%' │ '3952569 ± 47431' │ 340627 │ +│ 110 │ '+1212121212' │ '788.49 ± 2.09%' │ '705.00 ± 6.00' │ '1389465 ± 0.04%' │ '1418440 ± 12175' │ 126824 │ +│ 111 │ '+1212121212{"skipLike":{}}' │ '286.72 ± 2.42%' │ '251.00 ± 3.00' │ '3913998 ± 0.02%' │ '3984064 ± 48194' │ 348771 │ +│ 112 │ '+12 12' │ '490.54 ± 21.01%' │ '366.00 ± 6.00' │ '2682856 ± 0.03%' │ '2732240 ± 44068' │ 203858 │ +│ 113 │ ' +12 12 ' │ '535.89 ± 1.41%' │ '405.00 ± 25.00' │ '2136018 ± 0.11%' │ '2469136 ± 162443' │ 186607 │ +│ 114 │ ' +1212 ' │ '756.58 ± 14.05%' │ '596.00 ± 7.00' │ '1633654 ± 0.05%' │ '1677852 ± 19941' │ 132190 │ +│ 115 │ '+1212' │ '641.37 ± 7.27%' │ '549.00 ± 6.00' │ '1780440 ± 0.04%' │ '1821494 ± 20127' │ 155917 │ +│ 116 │ '+12.12' │ '791.92 ± 3.66%' │ '660.00 ± 10.00' │ '1471611 ± 0.06%' │ '1515152 ± 23310' │ 126276 │ +│ 117 │ '-12.12' │ '771.85 ± 1.61%' │ '680.00 ± 9.00' │ '1438367 ± 0.05%' │ '1470588 ± 19725' │ 129559 │ +│ 118 │ '-012.12' │ '891.87 ± 1.29%' │ '719.00 ± 18.00' │ '1307721 ± 0.10%' │ '1390821 ± 35713' │ 112124 │ +└─────────┴────────────────────────────────────────────────────────┴───────────────────┴──────────────────┴────────────────────────┴────────────────────────┴─────────┘ \ No newline at end of file diff --git a/strnum.js b/strnum.js index aec2f50..fc18964 100644 --- a/strnum.js +++ b/strnum.js @@ -20,9 +20,9 @@ const defaultOptions = { /** * @template {*} T - * @param {T} str - * @param {Options} options - * @returns {number|T} + * @param {T} str - The string to convert to a number. + * @param {Options} [options] - Options to control the conversion behavior. + * @returns {number|T} - The converted number or the original value if conversion is not applicable. */ export default function toNumber(str, options = {}) { options = Object.assign({}, defaultOptions, options); From 9d40e6761df0e473ab4ac581483984c18fd26003 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Tue, 15 Jul 2025 22:54:25 +0200 Subject: [PATCH 08/36] improve --- strnum.js | 68 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 54 insertions(+), 14 deletions(-) diff --git a/strnum.js b/strnum.js index fc18964..9d90267 100644 --- a/strnum.js +++ b/strnum.js @@ -1,5 +1,5 @@ -const hexRegex = /^[-+]?0x[a-fA-F0-9]+$/; -const numRegex = /^([\-\+])?(0*)([0-9]*(\.[0-9]*)?)$/; +const hexRegex = /^[-+]?0x[a-fA-F0-9]+$/u; +const numRegex = /^([\-\+])?(0*)([0-9]*(\.[0-9]*)?)$/u; /** * @typedef {Object} Options @@ -43,7 +43,7 @@ export default function toNumber(str, options = {}) { // +00.123 => [ , '+', '00', '.123', .. if (match) { const sign = match[1] || ""; - const leadingZeros = match[2]; + const leadingZeros = match[2] || ""; let numTrimmedByZeros = trimZeros(match[3]); //complete num without leading zeros const decimalAdjacentToLeadingZeros = sign ? // 0., -00., 000. str[leadingZeros.length + 1] === "." @@ -86,7 +86,7 @@ export default function toNumber(str, options = {}) { } } -const eNotationRegx = /^([-+])?(0*)(\d*(\.\d*)?[eE][-\+]?\d+)$/; +const eNotationRegx = /^([-+])?(0*)(\d*(\.\d*)?([eE])[-\+]?\d+)$/u; /** * @template {*} T @@ -100,7 +100,7 @@ function resolveEnotation(str, trimmedStr, options) { const notation = trimmedStr.match(eNotationRegx); if (notation) { let sign = notation[1] || ""; - const eChar = notation[3].indexOf("e") === -1 ? "E" : "e"; + const eChar = notation[5]; const leadingZeros = notation[2]; const eAdjacentToLeadingZeros = sign ? // 0E. str[leadingZeros.length + 1] === eChar @@ -108,7 +108,7 @@ function resolveEnotation(str, trimmedStr, options) { if (leadingZeros.length > 1 && eAdjacentToLeadingZeros) return str; else if (leadingZeros.length === 1 - && (notation[3].startsWith(`.${eChar}`) || notation[3][0] === eChar)) { + && ((notation[3][0] === '.' && notation[3][1] === eChar) || notation[3][0] === eChar)) { return Number(trimmedStr); } else if (options.leadingZeros && !eAdjacentToLeadingZeros) { //accept with leading zeros //remove leading 0s @@ -121,17 +121,57 @@ function resolveEnotation(str, trimmedStr, options) { } /** - * @param {string} numStr without leading zeros - * @returns {string} numStr with trimmed ending zeros + * @param {string} numStr numerical string with leading and trailing zeros + * @returns {string} numerical string with trimmed zeros */ function trimZeros(numStr) { - if (numStr.indexOf(".") !== -1) {//float - numStr = numStr.replace(/0+$/, ""); //remove ending zeros - if (numStr === ".") numStr = "0"; - else if (numStr[0] === ".") numStr = "0" + numStr; - else if (numStr[numStr.length - 1] === ".") numStr = numStr.substring(0, numStr.length - 1); - return numStr; + const dotPosition = numStr.indexOf("."); + + // not a float number + if (dotPosition === -1) return numStr; + + const len = numStr.length; + let trimStart = 0; + let trimEnd = len; + + let i = 0; + + if (dotPosition !== 0) { + while (i < dotPosition) { + if (numStr[i] !== "0") { + trimStart = i; + break; + } + i++; + } } + + i = len - 1; + if (dotPosition !== i) { + while (i >= dotPosition) { + if (numStr[i] !== "0") { + trimEnd = i + 1; //+1 to include the last non-zero digit + break; + } + i-- + } + } + + // If the dot is the last character + if (dotPosition === trimEnd - 1) { + --trimEnd + } + + // trimStart and trimEnd are the same, we know that the string is all zeros + if (trimStart === trimEnd) { + return "0"; //all zeros + } + + if (trimStart !== 0 || trimEnd !== len) { + numStr = numStr.slice(trimStart, trimEnd); + } + + if (dotPosition === 0) numStr = "0" + numStr; return numStr; } From f85c3261ce140f232a103d277a5f6756484f5c76 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Wed, 16 Jul 2025 10:23:20 +0200 Subject: [PATCH 09/36] improve --- README.md | 4 +- strnum.js | 440 +++++++++++++++++++++++++++++++++++++----------------- 2 files changed, 302 insertions(+), 142 deletions(-) diff --git a/README.md b/README.md index 419e8ef..aca3465 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,8 @@ npm install strnum const toNumber = require("strnum"); toNumber(undefined) // undefined -toNumber(null)) //null -toNumber("")) // "" +toNumber(null) //null +toNumber("") // "" toNumber("string"); //"string") toNumber("12,12"); //"12,12") toNumber("12 12"); //"12 12") diff --git a/strnum.js b/strnum.js index 9d90267..367bcd0 100644 --- a/strnum.js +++ b/strnum.js @@ -1,6 +1,3 @@ -const hexRegex = /^[-+]?0x[a-fA-F0-9]+$/u; -const numRegex = /^([\-\+])?(0*)([0-9]*(\.[0-9]*)?)$/u; - /** * @typedef {Object} Options * @property {boolean} [hex=true] - Whether to allow hexadecimal numbers (e.g., "0x1A"). @@ -18,6 +15,37 @@ const defaultOptions = { eNotation: true, }; +/** + * The character used for scientific notation in numbers, based on the environment. + * This is determined by checking if a large number can be represented in scientific notation. + * @type {"e"|"E"} + * @constant + */ +const EXP_CHAR = (function returnExpChar() { + const bigNumber = 1e1000; + const str = String(bigNumber); + return str.indexOf("e") === -1 ? "e" : "E"; +})() + +/** + * @type {(string: string, radix: 10|16) => number} + */ +const parse_int = ((function parse_int() { + if (parseInt) return parseInt + else if (Number.parseInt) return Number.parseInt + else if (window && window.parseInt) return window.parseInt + else return function parseInt() { + throw new Error("parseInt, Number.parseInt, window.parseInt are not supported") + }; +})()); + +const IS_EMPTY = 0; +const HAS_ERROR = 1 << 0; +const IS_HEX = 1 << 1; +const IS_FLOAT = 1 << 2; +const HAS_WHITESPACE = 1 << 3; +const HAS_E = 1 << 4; + /** * @template {*} T * @param {T} str - The string to convert to a number. @@ -25,164 +53,296 @@ const defaultOptions = { * @returns {number|T} - The converted number or the original value if conversion is not applicable. */ export default function toNumber(str, options = {}) { - options = Object.assign({}, defaultOptions, options); if (!str || typeof str !== "string") return str; - let trimmedStr = str.trim(); - - if (options.skipLike !== undefined && options.skipLike.test(trimmedStr)) return str; - else if (str === "0") return 0; - else if (options.hex && hexRegex.test(trimmedStr)) { - return parse_int(trimmedStr, 16); - } else if (trimmedStr.search(/.+[eE].+/) !== -1) { //eNotation - if (options.eNotation === false) return str; //skip eNotation{ - return resolveEnotation(str, trimmedStr, options); - } else { - //separate negative sign, leading zeros, and rest number - const match = numRegex.exec(trimmedStr); - // +00.123 => [ , '+', '00', '.123', .. - if (match) { - const sign = match[1] || ""; - const leadingZeros = match[2] || ""; - let numTrimmedByZeros = trimZeros(match[3]); //complete num without leading zeros - const decimalAdjacentToLeadingZeros = sign ? // 0., -00., 000. - str[leadingZeros.length + 1] === "." - : str[leadingZeros.length] === "."; - - //trim ending zeros for floating number - if (!options.leadingZeros //leading zeros are not allowed - && (leadingZeros.length > 1 - || (leadingZeros.length === 1 && !decimalAdjacentToLeadingZeros))) { - // 00, 00.3, +03.24, 03, 03.24 - return str; - } - else {//no leading zeros or leading zeros are allowed - const num = Number(trimmedStr); - const parsedStr = String(num); - - if (num === 0) return num; - if (parsedStr.search(/[eE]/) !== -1) { //given number is long and parsed to eNotation - if (options.eNotation) return num; - else return str; - } else if (trimmedStr.indexOf(".") !== -1) { //floating number - if (parsedStr === "0") return num; //0.0 - else if (parsedStr === numTrimmedByZeros) return num; //0.456. 0.79000 - else if (parsedStr === `${sign}${numTrimmedByZeros}`) return num; - else return str; - } + const analyzeResult = analyzeNumber(str, options); - let n = leadingZeros ? numTrimmedByZeros : trimmedStr; - if (leadingZeros) { - // -009 => -9 - return (n === parsedStr) || (sign + n === parsedStr) ? num : str - } else { - // +9 - return (n === parsedStr) || (n === sign + parsedStr) ? num : str - } - } - } else { //non-numeric string - return str; + if (options.skipLike !== undefined) { + const trimmedStr = ((analyzeResult & HAS_WHITESPACE) === HAS_WHITESPACE) + ? str.trim() : str; + if (options.skipLike.test(trimmedStr)) { + return str; // Skip conversion if it matches the skip pattern } } -} - -const eNotationRegx = /^([-+])?(0*)(\d*(\.\d*)?([eE])[-\+]?\d+)$/u; -/** - * @template {*} T - * @param {T} str - * @param {string} trimmedStr - * @param {object} options - * @param {boolean} [options.leadingZeros=true] - * @returns {number|T} - */ -function resolveEnotation(str, trimmedStr, options) { - const notation = trimmedStr.match(eNotationRegx); - if (notation) { - let sign = notation[1] || ""; - const eChar = notation[5]; - const leadingZeros = notation[2]; - const eAdjacentToLeadingZeros = sign ? // 0E. - str[leadingZeros.length + 1] === eChar - : str[leadingZeros.length] === eChar; - - if (leadingZeros.length > 1 && eAdjacentToLeadingZeros) return str; - else if (leadingZeros.length === 1 - && ((notation[3][0] === '.' && notation[3][1] === eChar) || notation[3][0] === eChar)) { - return Number(trimmedStr); - } else if (options.leadingZeros && !eAdjacentToLeadingZeros) { //accept with leading zeros - //remove leading 0s - trimmedStr = (notation[1] || "") + notation[3]; - return Number(trimmedStr); - } else return str; - } else { + if ((analyzeResult & HAS_ERROR) === HAS_ERROR) { return str; } -} - -/** - * @param {string} numStr numerical string with leading and trailing zeros - * @returns {string} numerical string with trimmed zeros - */ -function trimZeros(numStr) { - const dotPosition = numStr.indexOf("."); - // not a float number - if (dotPosition === -1) return numStr; - - const len = numStr.length; - let trimStart = 0; - let trimEnd = len; - - let i = 0; - - if (dotPosition !== 0) { - while (i < dotPosition) { - if (numStr[i] !== "0") { - trimStart = i; - break; - } - i++; - } + if ((analyzeResult & IS_HEX) === IS_HEX) { + // If the string contains 'x', it is likely a hexadecimal number. + return parse_int(str, 16); } - i = len - 1; - if (dotPosition !== i) { - while (i >= dotPosition) { - if (numStr[i] !== "0") { - trimEnd = i + 1; //+1 to include the last non-zero digit - break; - } - i-- + if ((analyzeResult & HAS_E) === HAS_E) { + if (options.eNotation !== false) { + return Number(str); } + return str; // If scientific notation is not allowed, return the original string } - // If the dot is the last character - if (dotPosition === trimEnd - 1) { - --trimEnd + const num = Number(str); + const parsedStr = String(num); + + if (parsedStr.indexOf(EXP_CHAR) !== -1) { //given number is long and parsed to eNotation + if (options.eNotation !== false) return num; + else return str; } - // trimStart and trimEnd are the same, we know that the string is all zeros - if (trimStart === trimEnd) { - return "0"; //all zeros + if (((analyzeResult & IS_FLOAT) === IS_EMPTY) && (num > Number.MAX_SAFE_INTEGER || num < Number.MIN_SAFE_INTEGER)) { + return str; // If the number is out of safe integer range, return the original string } - if (trimStart !== 0 || trimEnd !== len) { - numStr = numStr.slice(trimStart, trimEnd); + if ((analyzeResult & IS_FLOAT) === IS_FLOAT) { + const parsedDecimalPoint = parsedStr.indexOf(".") + 1; + + if ((parsedStr.length - parsedDecimalPoint) > 10) { + const strDecimalPoint = str.indexOf(options.decimalPoint || ".") + 1; + + for (let i = 0; i < parsedStr.length; i++) { + if (parsedStr[parsedDecimalPoint + i] !== str[strDecimalPoint + i]) { + return str; // If the decimal part does not match, return the original string + } + } + } } - if (dotPosition === 0) numStr = "0" + numStr; - return numStr; + return num; } +const ERROR = -1; // error state +const BEGIN = 0; // initial state +const OWS = 1; // optional whitespace +const SIGN = 2; // sign +const LEADING_ZEROS = 3; // leading zeros +const INT = 4; // integer partEXP +const BEGIN_FRAC = 5; // beginning of fractional part +const FRAC = 6; // fractional part +const EXP = 7; // exponent part +const TRAILING_ZEROS = 8; // trailing zeros +const TRAILING_SPACE = 9; // trailing space +const HEX = 10; // hexadecimal state + /** - * @type {(string: string, radix: 16) => number} + * @param {string} str + * @param {Options} options - Options to control the parsing behavior. + * @returns {number} */ -const parse_int = ((function parse_int() { - if (parseInt) return parseInt - else if (Number.parseInt) return Number.parseInt - else if (window && window.parseInt) return window.parseInt - else return function parseInt() { - throw new Error("parseInt, Number.parseInt, window.parseInt are not supported") - }; -})()); +function analyzeNumber(str, options) { + let len = str.length; + + let state = BEGIN; + let start = 0; + let length = 0 + let trailingZeros = 0; + let i = 0; + + let result = IS_EMPTY; + + const ON_HEX = options.hex !== false ? HEX : ERROR; + const DECIMAL = options.decimalPoint || "\."; + const ON_E = options.eNotation !== false ? EXP : ERROR; + const NO_LEADING_ZEROS = options.leadingZeros === false; + + while (i < len) { + switch (str[i]) { + case " ": + switch (state) { + case BEGIN: + ++i; + result |= HAS_WHITESPACE; + state = OWS; + continue; + case OWS: + case TRAILING_SPACE: + ++i; + continue; + case INT: + case FRAC: + ++i; + result |= HAS_WHITESPACE; + state = TRAILING_SPACE; + continue; + default: + return result | HAS_ERROR; + } + case "+": + case "-": + switch (state) { + case BEGIN: + case OWS: + case SIGN: + state = LEADING_ZEROS; + start = ++i; + break; + case EXP: + if (length === 0) { + start = ++i; + ++length; + continue; + } + default: + return result | HAS_ERROR; + } + break; + case "0": + switch (state) { + case BEGIN: + state = LEADING_ZEROS; + start = ++i; + length = 1; + break; + case LEADING_ZEROS: + if (NO_LEADING_ZEROS && length === 1) { + return result | HAS_ERROR; + } + ++length; + ++start; + ++i; + continue; + case OWS: + case SIGN: + ++length; + ++start; + ++i + state = LEADING_ZEROS; + continue; + case BEGIN_FRAC: + result |= IS_FLOAT; + state = FRAC; + case FRAC: + trailingZeros++; + case INT: + ++i; + continue; + default: + return result | HAS_ERROR; + } + break; + case "x": + switch (state) { + case LEADING_ZEROS: + if (length !== 1) { + return result | HAS_ERROR; + } + start = ++i; + length = 0; + result |= IS_HEX; + state = ON_HEX; + continue; + default: + return result | HAS_ERROR; + } + case "1": + case "2": + case "3": + case "4": + case "5": + case "6": + case "7": + case "8": + case "9": + switch (state) { + case LEADING_ZEROS: + if (NO_LEADING_ZEROS && length === 1) { + return result | HAS_ERROR; + } + case BEGIN: + case OWS: + case SIGN: + state = INT; + case HEX: + case INT: + case EXP: + ++i; + ++length; + break; + case BEGIN_FRAC: + result |= IS_FLOAT; + state = FRAC; + case FRAC: + trailingZeros = 0; + ++length; + ++i; + break; + default: + return result | HAS_ERROR; + } + break; + case "a": + case "b": + case "c": + case "d": + case "f": + case "A": + case "B": + case "C": + case "D": + case "F": + switch (state) { + case HEX: + ++i; + ++length; + break; + default: + return result | HAS_ERROR; + } + break; + case DECIMAL: + switch (state) { + case BEGIN: + case LEADING_ZEROS: + case OWS: + case SIGN: + case INT: + start = ++i; + length = 0; + state = BEGIN_FRAC; + break; + default: + return result | HAS_ERROR; + } + break; + case "e": + case "E": + switch (state) { + case LEADING_ZEROS: + if (length > 1) { + return result | HAS_ERROR; + } + case INT: + case BEGIN_FRAC: + case FRAC: + length = 0; + start = ++i; + result |= HAS_E; + state = ON_E; + break; + case HEX: + ++i; + ++length; + break; + default: + return result | HAS_ERROR; + } + break; + default: + return result | HAS_ERROR; + } + } + + switch (state) { + case LEADING_ZEROS: + case INT: + case BEGIN_FRAC: + case FRAC: + case TRAILING_ZEROS: + case TRAILING_SPACE: + return result; + case EXP: + case HEX: + return length === 0 ? result | HAS_ERROR : result; + default: + return result | HAS_ERROR; + } +} From 973d1b8af55346d3d06275255c03b5099050b1a0 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Wed, 16 Jul 2025 11:11:37 +0200 Subject: [PATCH 10/36] remove benchmark log --- bench.orig.txt | 124 ------------------------------------------------- 1 file changed, 124 deletions(-) delete mode 100644 bench.orig.txt diff --git a/bench.orig.txt b/bench.orig.txt deleted file mode 100644 index 7c3e2ee..0000000 --- a/bench.orig.txt +++ /dev/null @@ -1,124 +0,0 @@ -strnum benchmark -┌─────────┬────────────────────────────────────────────────────────┬───────────────────┬──────────────────┬────────────────────────┬────────────────────────┬─────────┐ -│ (index) │ Task name │ Latency avg (ns) │ Latency med (ns) │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │ -├─────────┼────────────────────────────────────────────────────────┼───────────────────┼──────────────────┼────────────────────────┼────────────────────────┼─────────┤ -│ 0 │ 'undefined' │ '176.28 ± 0.75%' │ '156.00 ± 2.00' │ '6271038 ± 0.02%' │ '6410256 ± 83250' │ 567292 │ -│ 1 │ 'null' │ '188.77 ± 9.86%' │ '157.00 ± 3.00' │ '6197657 ± 0.02%' │ '6369427 ± 119427' │ 529734 │ -│ 2 │ '' │ '175.96 ± 0.70%' │ '156.00 ± 2.00' │ '6238677 ± 0.03%' │ '6410257 ± 83250' │ 568354 │ -│ 3 │ 'string' │ '401.63 ± 2.70%' │ '339.00 ± 8.00' │ '2872819 ± 0.04%' │ '2949853 ± 71296' │ 248987 │ -│ 4 │ 'e89794659669cb7bb967db73a7ea6889c3891727' │ '469.05 ± 1.54%' │ '398.00 ± 7.00' │ '2437276 ± 0.04%' │ '2512563 ± 44982' │ 213196 │ -│ 5 │ '12,12' │ '374.09 ± 1.62%' │ '331.00 ± 4.00' │ '2964088 ± 0.03%' │ '3021148 ± 36956' │ 267317 │ -│ 6 │ '12 12' │ '386.05 ± 1.67%' │ '328.00 ± 6.00' │ '2947028 ± 0.04%' │ '3048781 ± 54769' │ 259055 │ -│ 7 │ '12-12' │ '412.18 ± 16.46%' │ '328.00 ± 5.00' │ '2975623 ± 0.03%' │ '3048780 ± 47195' │ 242613 │ -│ 8 │ '12.12.12' │ '754.10 ± 2.23%' │ '794.00 ± 40.00' │ '1452149 ± 0.16%' │ '1259446 ± 60405' │ 132610 │ -│ 9 │ '+12' │ '577.84 ± 1.38%' │ '530.00 ± 5.00' │ '1842993 ± 0.04%' │ '1886792 ± 17969' │ 173060 │ -│ 10 │ '+ 12' │ '357.10 ± 1.47%' │ '321.00 ± 4.00' │ '3054220 ± 0.03%' │ '3115265 ± 39309' │ 280036 │ -│ 11 │ '12+12' │ '396.32 ± 1.61%' │ '331.00 ± 6.00' │ '2883455 ± 0.05%' │ '3021148 ± 55775' │ 252323 │ -│ 12 │ '1212+' │ '389.35 ± 1.55%' │ '340.00 ± 6.00' │ '2858365 ± 0.04%' │ '2941176 ± 52836' │ 256837 │ -│ 13 │ '0x2f' │ '260.14 ± 0.55%' │ '242.00 ± 3.00' │ '4062944 ± 0.02%' │ '4132231 ± 50599' │ 384403 │ -│ 14 │ '-0x2f' │ '296.43 ± 16.57%' │ '246.00 ± 3.00' │ '3982511 ± 0.03%' │ '4065041 ± 50186' │ 337354 │ -│ 15 │ '0x2f{"hex":true}' │ '273.09 ± 0.50%' │ '258.00 ± 3.00' │ '3830156 ± 0.02%' │ '3875969 ± 45600' │ 366184 │ -│ 16 │ '-0x2f{"hex":true}' │ '278.65 ± 0.61%' │ '257.00 ± 3.00' │ '3829122 ± 0.02%' │ '3891051 ± 45957' │ 358876 │ -│ 17 │ '0x2f{"hex":false}' │ '337.83 ± 0.70%' │ '305.00 ± 4.00' │ '3218980 ± 0.02%' │ '3278689 ± 43571' │ 296005 │ -│ 18 │ '-0x2f{"hex":false}' │ '401.92 ± 0.76%' │ '342.00 ± 9.00' │ '2791745 ± 0.05%' │ '2923977 ± 79026' │ 248807 │ -│ 19 │ '0xzz' │ '360.92 ± 2.76%' │ '322.00 ± 4.00' │ '3047684 ± 0.03%' │ '3105590 ± 39064' │ 277070 │ -│ 20 │ 'iweraf0x123qwerqwer' │ '352.81 ± 2.37%' │ '315.00 ± 5.00' │ '3112495 ± 0.03%' │ '3174603 ± 49603' │ 283579 │ -│ 21 │ '1230x55' │ '442.30 ± 1.67%' │ '377.00 ± 9.00' │ '2568964 ± 0.05%' │ '2652520 ± 61846' │ 226093 │ -│ 22 │ 'JVBERi0xLjMNCiXi48' │ '419.78 ± 1.82%' │ '356.00 ± 8.00' │ '2723575 ± 0.04%' │ '2808989 ± 61736' │ 238222 │ -│ 23 │ '0' │ '197.89 ± 0.72%' │ '174.00 ± 3.00' │ '5584882 ± 0.03%' │ '5747126 ± 100827' │ 505333 │ -│ 24 │ '00' │ '423.53 ± 1.44%' │ '382.00 ± 5.00' │ '2561154 ± 0.03%' │ '2617801 ± 33822' │ 236112 │ -│ 25 │ '00.0' │ '595.90 ± 0.71%' │ '532.00 ± 7.00' │ '1830700 ± 0.04%' │ '1879699 ± 25063' │ 167813 │ -│ 26 │ '0{"leadingZeros":false}' │ '211.07 ± 0.68%' │ '191.00 ± 3.00' │ '5140805 ± 0.02%' │ '5235602 ± 83547' │ 473773 │ -│ 27 │ '00{"leadingZeros":false}' │ '424.16 ± 3.16%' │ '360.00 ± 5.00' │ '2716280 ± 0.03%' │ '2777778 ± 39124' │ 235761 │ -│ 28 │ '00.0{"leadingZeros":false}' │ '555.64 ± 0.70%' │ '498.00 ± 6.00' │ '1959999 ± 0.04%' │ '2008032 ± 24488' │ 179974 │ -│ 29 │ '06' │ '500.89 ± 1.79%' │ '454.00 ± 4.00' │ '2161924 ± 0.03%' │ '2202643 ± 19579' │ 199646 │ -│ 30 │ '06{"leadingZeros":true}' │ '532.38 ± 2.86%' │ '468.00 ± 6.00' │ '2087822 ± 0.04%' │ '2136752 ± 27048' │ 187836 │ -│ 31 │ '06{"leadingZeros":false}' │ '447.79 ± 2.50%' │ '389.00 ± 5.00' │ '2522762 ± 0.03%' │ '2570694 ± 33473' │ 223319 │ -│ 32 │ '006' │ '612.60 ± 27.68%' │ '468.00 ± 5.00' │ '2090950 ± 0.04%' │ '2136752 ± 22587' │ 175858 │ -│ 33 │ '006{"leadingZeros":true}' │ '545.63 ± 9.20%' │ '480.00 ± 5.00' │ '2046745 ± 0.03%' │ '2083333 ± 21478' │ 183276 │ -│ 34 │ '006{"leadingZeros":false}' │ '447.74 ± 9.36%' │ '402.00 ± 5.00' │ '2448348 ± 0.03%' │ '2487562 ± 31329' │ 223344 │ -│ 35 │ '000000000000000000000000017717{"leadingZeros":false}' │ '1800.8 ± 2.15%' │ '1652.0 ± 9.00' │ '597184 ± 0.07%' │ '605327 ± 3316' │ 55530 │ -│ 36 │ '000000000000000000000000017717{"leadingZeros":true}' │ '1967.2 ± 1.03%' │ '1801.0 ± 9.00' │ '545915 ± 0.08%' │ '555247 ± 2761' │ 50836 │ -│ 37 │ '0420926189200190257681175017717' │ '2244.9 ± 4.93%' │ '1993.0 ± 13.00' │ '494292 ± 0.09%' │ '501756 ± 3294' │ 44545 │ -│ 38 │ '20.21.030' │ '477.83 ± 2.10%' │ '427.00 ± 5.00' │ '2306784 ± 0.03%' │ '2341920 ± 27748' │ 209280 │ -│ 39 │ '0.21.030' │ '479.55 ± 1.35%' │ '427.00 ± 6.00' │ '2300233 ± 0.03%' │ '2341920 ± 33377' │ 208527 │ -│ 40 │ '0.21.' │ '401.30 ± 1.58%' │ '372.00 ± 5.00' │ '2644391 ± 0.03%' │ '2688172 ± 35652' │ 249192 │ -│ 41 │ '0.' │ '520.13 ± 2.69%' │ '475.00 ± 6.00' │ '2054102 ± 0.03%' │ '2105263 ± 26933' │ 192260 │ -│ 42 │ '+0.' │ '549.97 ± 1.58%' │ '497.00 ± 7.00' │ '1961545 ± 0.03%' │ '2012072 ± 28744' │ 181829 │ -│ 43 │ '-0.' │ '589.83 ± 2.36%' │ '518.00 ± 8.00' │ '1887212 ± 0.04%' │ '1930502 ± 30282' │ 169540 │ -│ 44 │ '1.' │ '615.35 ± 2.23%' │ '551.00 ± 6.00' │ '1773254 ± 0.04%' │ '1814882 ± 19980' │ 162510 │ -│ 45 │ '00.00' │ '640.34 ± 8.32%' │ '553.00 ± 7.00' │ '1768126 ± 0.04%' │ '1808318 ± 23184' │ 156168 │ -│ 46 │ '0.06' │ '702.87 ± 5.95%' │ '620.00 ± 10.00' │ '1576319 ± 0.04%' │ '1612903 ± 25602' │ 142275 │ -│ 47 │ '00.6' │ '707.26 ± 2.64%' │ '619.00 ± 11.00' │ '1576901 ± 0.04%' │ '1615509 ± 29228' │ 141390 │ -│ 48 │ '.006' │ '698.30 ± 2.20%' │ '615.00 ± 13.00' │ '1590474 ± 0.05%' │ '1626016 ± 35113' │ 143205 │ -│ 49 │ '6.0' │ '650.21 ± 1.17%' │ '593.00 ± 12.00' │ '1646076 ± 0.04%' │ '1686341 ± 33448' │ 153798 │ -│ 50 │ '06.0' │ '720.14 ± 3.88%' │ '631.00 ± 14.00' │ '1551427 ± 0.04%' │ '1584786 ± 35960' │ 138862 │ -│ 51 │ '0.0{"leadingZeros":false}' │ '590.67 ± 1.67%' │ '531.00 ± 6.00' │ '1841661 ± 0.04%' │ '1883239 ± 21523' │ 169301 │ -│ 52 │ '00.00{"leadingZeros":false}' │ '550.61 ± 1.83%' │ '514.00 ± 5.00' │ '1913232 ± 0.03%' │ '1945525 ± 18743' │ 181618 │ -│ 53 │ '0.06{"leadingZeros":false}' │ '694.58 ± 0.63%' │ '635.00 ± 10.00' │ '1541587 ± 0.04%' │ '1574803 ± 25197' │ 143972 │ -│ 54 │ '00.6{"leadingZeros":false}' │ '527.42 ± 1.71%' │ '484.00 ± 5.00' │ '2025904 ± 0.03%' │ '2066116 ± 21567' │ 189603 │ -│ 55 │ '.006{"leadingZeros":false}' │ '698.97 ± 1.89%' │ '619.00 ± 8.00' │ '1575767 ± 0.04%' │ '1615509 ± 20613' │ 143851 │ -│ 56 │ '6.0{"leadingZeros":false}' │ '654.77 ± 1.51%' │ '606.00 ± 10.00' │ '1613908 ± 0.04%' │ '1650165 ± 26788' │ 152725 │ -│ 57 │ '06.0{"leadingZeros":false}' │ '588.63 ± 9.69%' │ '519.00 ± 5.00' │ '1893840 ± 0.03%' │ '1926782 ± 18743' │ 169885 │ -│ 58 │ '+06' │ '549.03 ± 1.86%' │ '500.00 ± 8.00' │ '1950534 ± 0.03%' │ '2000000 ± 32520' │ 182141 │ -│ 59 │ '-06' │ '547.42 ± 2.54%' │ '495.00 ± 6.00' │ '1975987 ± 0.04%' │ '2020202 ± 24788' │ 182676 │ -│ 60 │ '-06{"leadingZeros":true}' │ '552.08 ± 1.98%' │ '503.00 ± 5.00' │ '1949603 ± 0.03%' │ '1988072 ± 19568' │ 181135 │ -│ 61 │ '-06{"leadingZeros":false}' │ '460.01 ± 10.73%' │ '399.00 ± 4.00' │ '2466025 ± 0.03%' │ '2506266 ± 25380' │ 217389 │ -│ 62 │ '-0.0' │ '692.25 ± 14.45%' │ '561.00 ± 10.00' │ '1734246 ± 0.05%' │ '1782531 ± 32351' │ 144458 │ -│ 63 │ '-00.00' │ '638.14 ± 0.52%' │ '596.00 ± 6.00' │ '1643582 ± 0.03%' │ '1677852 ± 17063' │ 156705 │ -│ 64 │ '-0.06' │ '757.29 ± 4.69%' │ '674.00 ± 12.00' │ '1451495 ± 0.04%' │ '1483680 ± 25954' │ 132051 │ -│ 65 │ '-00.6' │ '932.64 ± 2.03%' │ '724.00 ± 41.00' │ '1205406 ± 0.14%' │ '1381215 ± 82913' │ 107223 │ -│ 66 │ '-.006' │ '750.61 ± 2.10%' │ '681.00 ± 13.00' │ '1447778 ± 0.04%' │ '1468429 ± 28577' │ 133226 │ -│ 67 │ '-6.0' │ '729.81 ± 0.68%' │ '651.00 ± 10.00' │ '1488765 ± 0.05%' │ '1536098 ± 23964' │ 137023 │ -│ 68 │ '-06.0' │ '774.08 ± 7.77%' │ '672.00 ± 10.00' │ '1453810 ± 0.05%' │ '1488095 ± 21820' │ 129185 │ -│ 69 │ '+06.0' │ '735.38 ± 2.33%' │ '642.00 ± 10.00' │ '1514921 ± 0.05%' │ '1557632 ± 24646' │ 135984 │ -│ 70 │ '-0.0{"leadingZeros":false}' │ '638.38 ± 1.26%' │ '571.00 ± 6.00' │ '1702628 ± 0.05%' │ '1751313 ± 18598' │ 156646 │ -│ 71 │ '-00.00{"leadingZeros":false}' │ '627.92 ± 3.64%' │ '541.00 ± 5.00' │ '1810988 ± 0.04%' │ '1848429 ± 17243' │ 159256 │ -│ 72 │ '-0.06{"leadingZeros":false}' │ '747.75 ± 2.00%' │ '679.00 ± 7.00' │ '1443515 ± 0.04%' │ '1472754 ± 15341' │ 133735 │ -│ 73 │ '-00.6{"leadingZeros":false}' │ '568.70 ± 1.62%' │ '512.00 ± 5.00' │ '1915025 ± 0.03%' │ '1953125 ± 19262' │ 175840 │ -│ 74 │ '-.006{"leadingZeros":false}' │ '850.33 ± 2.64%' │ '684.00 ± 11.00' │ '1361597 ± 0.10%' │ '1461988 ± 23139' │ 117602 │ -│ 75 │ '-6.0{"leadingZeros":false}' │ '760.93 ± 3.57%' │ '661.00 ± 9.00' │ '1477477 ± 0.05%' │ '1512859 ± 20883' │ 131419 │ -│ 76 │ '-06.0{"leadingZeros":false}' │ '595.55 ± 4.24%' │ '538.00 ± 5.00' │ '1826741 ± 0.03%' │ '1858736 ± 17437' │ 167914 │ -│ 77 │ '020211201030005811824' │ '1345.6 ± 1.29%' │ '1254.0 ± 11.00' │ '786619 ± 0.05%' │ '797448 ± 7057' │ 74316 │ -│ 78 │ '20211201030005811824' │ '1224.5 ± 1.48%' │ '1118.0 ± 11.00' │ '878751 ± 0.06%' │ '894454 ± 8888' │ 81665 │ -│ 79 │ '20.211201030005811824' │ '1403.1 ± 1.47%' │ '1267.0 ± 14.00' │ '771738 ± 0.07%' │ '789266 ± 8819' │ 71272 │ -│ 80 │ '0.211201030005811824' │ '1375.4 ± 4.39%' │ '1232.0 ± 21.00' │ '797888 ± 0.06%' │ '811688 ± 14076' │ 72707 │ -│ 81 │ '01.0e2{"leadingZeros":false}' │ '509.33 ± 2.99%' │ '426.00 ± 5.00' │ '2213375 ± 0.07%' │ '2347418 ± 27879' │ 196337 │ -│ 82 │ '-01.0e2{"leadingZeros":false}' │ '506.44 ± 7.77%' │ '439.00 ± 4.00' │ '2231913 ± 0.03%' │ '2277904 ± 20946' │ 197456 │ -│ 83 │ '01.0e2' │ '566.50 ± 3.40%' │ '490.00 ± 5.00' │ '1997240 ± 0.04%' │ '2040816 ± 21039' │ 176522 │ -│ 84 │ '-01.0e2' │ '585.88 ± 2.57%' │ '511.00 ± 5.00' │ '1913301 ± 0.04%' │ '1956947 ± 19337' │ 170682 │ -│ 85 │ '1.0e2' │ '502.10 ± 2.12%' │ '441.00 ± 4.00' │ '2221158 ± 0.03%' │ '2267574 ± 20756' │ 199270 │ -│ 86 │ '-1.0e2' │ '557.07 ± 3.24%' │ '489.00 ± 4.00' │ '2002467 ± 0.03%' │ '2044990 ± 16866' │ 179511 │ -│ 87 │ '1.0e-2' │ '507.55 ± 1.65%' │ '458.00 ± 5.00' │ '2139581 ± 0.03%' │ '2183406 ± 24099' │ 197025 │ -│ 88 │ '420926189200190257681175017717' │ '2003.1 ± 1.14%' │ '1852.0 ± 11.00' │ '531529 ± 0.08%' │ '539957 ± 3226' │ 49923 │ -│ 89 │ '420926189200190257681175017717{"eNotation":false}' │ '2252.9 ± 1.38%' │ '1885.0 ± 21.00' │ '495379 ± 0.16%' │ '530504 ± 5845' │ 44387 │ -│ 90 │ '1e-2' │ '531.57 ± 4.00%' │ '435.00 ± 6.00' │ '2167114 ± 0.07%' │ '2298851 ± 32152' │ 188126 │ -│ 91 │ '1e+2' │ '487.80 ± 10.17%' │ '422.00 ± 4.00' │ '2326420 ± 0.03%' │ '2369668 ± 22676' │ 205002 │ -│ 92 │ '1.e+2' │ '495.84 ± 3.41%' │ '441.00 ± 4.00' │ '2216706 ± 0.04%' │ '2267574 ± 20383' │ 201678 │ -│ 93 │ '01.0E2{"leadingZeros":false}' │ '474.01 ± 1.21%' │ '426.00 ± 5.00' │ '2264958 ± 0.05%' │ '2347418 ± 27232' │ 210968 │ -│ 94 │ '-01.0E2{"leadingZeros":false}' │ '479.89 ± 0.63%' │ '440.00 ± 4.00' │ '2232908 ± 0.03%' │ '2272727 ± 20851' │ 208380 │ -│ 95 │ '01.0E2' │ '563.06 ± 3.63%' │ '490.00 ± 4.00' │ '2003573 ± 0.03%' │ '2040816 ± 16797' │ 177602 │ -│ 96 │ '-01.0E2' │ '591.72 ± 6.69%' │ '509.00 ± 5.00' │ '1913905 ± 0.04%' │ '1964637 ± 19490' │ 169023 │ -│ 97 │ '1.0E2' │ '488.81 ± 1.93%' │ '438.00 ± 4.00' │ '2227179 ± 0.04%' │ '2283105 ± 21042' │ 204580 │ -│ 98 │ '-1.0E2' │ '574.41 ± 2.99%' │ '486.00 ± 6.00' │ '1994244 ± 0.05%' │ '2057613 ± 25093' │ 174093 │ -│ 99 │ '1.0E-2' │ '514.82 ± 2.64%' │ '457.00 ± 6.00' │ '2136942 ± 0.04%' │ '2188184 ± 29111' │ 194242 │ -│ 100 │ 'E-2' │ '337.80 ± 3.29%' │ '291.00 ± 4.00' │ '3368998 ± 0.03%' │ '3436426 ± 46596' │ 296030 │ -│ 101 │ 'E2' │ '342.97 ± 4.02%' │ '284.00 ± 4.00' │ '3436589 ± 0.03%' │ '3521127 ± 50302' │ 292899 │ -│ 102 │ '0E2' │ '531.56 ± 2.58%' │ '437.00 ± 6.00' │ '2154611 ± 0.07%' │ '2288330 ± 31856' │ 188128 │ -│ 103 │ '-0E2' │ '525.57 ± 3.34%' │ '463.00 ± 5.00' │ '2112493 ± 0.03%' │ '2159827 ± 23579' │ 190269 │ -│ 104 │ '00E2' │ '428.53 ± 2.14%' │ '380.00 ± 5.00' │ '2558436 ± 0.04%' │ '2631579 ± 35088' │ 233356 │ -│ 105 │ '00E2{"leadingZeros":false}' │ '446.23 ± 3.37%' │ '391.00 ± 4.00' │ '2507832 ± 0.03%' │ '2557545 ± 26435' │ 224101 │ -│ 106 │ '0{"skipLike":{}}' │ '275.63 ± 4.39%' │ '230.00 ± 3.00' │ '4263003 ± 0.02%' │ '4347826 ± 55981' │ 362807 │ -│ 107 │ '+12{"skipLike":{}}' │ '796.27 ± 26.20%' │ '566.00 ± 8.00' │ '1722058 ± 0.05%' │ '1766784 ± 25330' │ 125586 │ -│ 108 │ '12+12{"skipLike":{}}' │ '432.11 ± 2.99%' │ '379.00 ± 5.00' │ '2588268 ± 0.03%' │ '2638522 ± 35274' │ 231421 │ -│ 109 │ '12+1212121212{"skipLike":{}}' │ '295.40 ± 4.34%' │ '253.00 ± 3.00' │ '3895165 ± 0.02%' │ '3952569 ± 47431' │ 340627 │ -│ 110 │ '+1212121212' │ '788.49 ± 2.09%' │ '705.00 ± 6.00' │ '1389465 ± 0.04%' │ '1418440 ± 12175' │ 126824 │ -│ 111 │ '+1212121212{"skipLike":{}}' │ '286.72 ± 2.42%' │ '251.00 ± 3.00' │ '3913998 ± 0.02%' │ '3984064 ± 48194' │ 348771 │ -│ 112 │ '+12 12' │ '490.54 ± 21.01%' │ '366.00 ± 6.00' │ '2682856 ± 0.03%' │ '2732240 ± 44068' │ 203858 │ -│ 113 │ ' +12 12 ' │ '535.89 ± 1.41%' │ '405.00 ± 25.00' │ '2136018 ± 0.11%' │ '2469136 ± 162443' │ 186607 │ -│ 114 │ ' +1212 ' │ '756.58 ± 14.05%' │ '596.00 ± 7.00' │ '1633654 ± 0.05%' │ '1677852 ± 19941' │ 132190 │ -│ 115 │ '+1212' │ '641.37 ± 7.27%' │ '549.00 ± 6.00' │ '1780440 ± 0.04%' │ '1821494 ± 20127' │ 155917 │ -│ 116 │ '+12.12' │ '791.92 ± 3.66%' │ '660.00 ± 10.00' │ '1471611 ± 0.06%' │ '1515152 ± 23310' │ 126276 │ -│ 117 │ '-12.12' │ '771.85 ± 1.61%' │ '680.00 ± 9.00' │ '1438367 ± 0.05%' │ '1470588 ± 19725' │ 129559 │ -│ 118 │ '-012.12' │ '891.87 ± 1.29%' │ '719.00 ± 18.00' │ '1307721 ± 0.10%' │ '1390821 ± 35713' │ 112124 │ -└─────────┴────────────────────────────────────────────────────────┴───────────────────┴──────────────────┴────────────────────────┴────────────────────────┴─────────┘ \ No newline at end of file From eb541a655c2e9ccbf4ed892765deb1e42273938c Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Wed, 16 Jul 2025 11:39:25 +0200 Subject: [PATCH 11/36] improve --- package-lock.json | 17 +++++- package.json | 9 ++- strnum.d.ts | 31 +++++++++++ strnum.js | 137 +++++++++++++++++++++++----------------------- tsconfig.json | 13 +++++ 5 files changed, 135 insertions(+), 72 deletions(-) create mode 100644 strnum.d.ts create mode 100644 tsconfig.json diff --git a/package-lock.json b/package-lock.json index 6e84ec3..b4920e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,8 @@ "license": "MIT", "devDependencies": { "jasmine": "^5.6.0", - "tinybench": "^4.0.1" + "tinybench": "^4.0.1", + "typescript": "^5.8.3" } }, "node_modules/@isaacs/cliui": { @@ -449,6 +450,20 @@ "node": ">=18.0.0" } }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 65b6bac..7c7c322 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,10 @@ "description": "Parse String to Number based on configuration", "type": "module", "main": "strnum.js", + "types": "strnum.d.ts", "scripts": { - "test": "jasmine strnum.test.js" + "test": "jasmine strnum.test.js", + "types": "tsc -p ." }, "keywords": [ "string", @@ -27,6 +29,7 @@ ], "devDependencies": { "jasmine": "^5.6.0", - "tinybench": "^4.0.1" + "tinybench": "^4.0.1", + "typescript": "^5.8.3" } -} +} \ No newline at end of file diff --git a/strnum.d.ts b/strnum.d.ts new file mode 100644 index 0000000..267647c --- /dev/null +++ b/strnum.d.ts @@ -0,0 +1,31 @@ +declare module "strnum" { + /** + * @template {*} T + * @param {T} str - The string to convert to a number. + * @param {Options} [options] - Options to control the conversion behavior. + * @returns {number|T} - The converted number or the original value if conversion is not applicable. + */ + export default function toNumber(str: T, options?: Options): number | T; + export type Options = { + /** + * - Whether to allow hexadecimal numbers (e.g., "0x1A"). + */ + hex?: boolean; + /** + * - Whether to allow leading zeros in numbers. + */ + leadingZeros?: boolean; + /** + * - A regular expression to skip certain string patterns. + */ + skipLike?: RegExp; + /** + * - The character used as the decimal point. + */ + decimalPoint?: string; + /** + * - Whether to allow scientific notation (e.g., "1e10"). + */ + eNotation?: boolean; + }; +} diff --git a/strnum.js b/strnum.js index 367bcd0..7ecf0c8 100644 --- a/strnum.js +++ b/strnum.js @@ -25,22 +25,22 @@ const EXP_CHAR = (function returnExpChar() { const bigNumber = 1e1000; const str = String(bigNumber); return str.indexOf("e") === -1 ? "e" : "E"; -})() +})(); /** * @type {(string: string, radix: 10|16) => number} */ const parse_int = ((function parse_int() { - if (parseInt) return parseInt - else if (Number.parseInt) return Number.parseInt - else if (window && window.parseInt) return window.parseInt + if (parseInt) return parseInt; + else if (Number.parseInt) return Number.parseInt; + else if (window && window.parseInt) return window.parseInt; else return function parseInt() { throw new Error("parseInt, Number.parseInt, window.parseInt are not supported") }; })()); -const IS_EMPTY = 0; -const HAS_ERROR = 1 << 0; +const ERROR = 0; +const IS_EMPTY = 1 << 0; const IS_HEX = 1 << 1; const IS_FLOAT = 1 << 2; const HAS_WHITESPACE = 1 << 3; @@ -59,18 +59,18 @@ export default function toNumber(str, options = {}) { if (options.skipLike !== undefined) { const trimmedStr = ((analyzeResult & HAS_WHITESPACE) === HAS_WHITESPACE) - ? str.trim() : str; + ? str.trim() + : str; if (options.skipLike.test(trimmedStr)) { - return str; // Skip conversion if it matches the skip pattern + return str; } } - if ((analyzeResult & HAS_ERROR) === HAS_ERROR) { + if (analyzeResult === ERROR) { return str; } if ((analyzeResult & IS_HEX) === IS_HEX) { - // If the string contains 'x', it is likely a hexadecimal number. return parse_int(str, 16); } @@ -78,19 +78,20 @@ export default function toNumber(str, options = {}) { if (options.eNotation !== false) { return Number(str); } - return str; // If scientific notation is not allowed, return the original string + return str; } const num = Number(str); const parsedStr = String(num); - if (parsedStr.indexOf(EXP_CHAR) !== -1) { //given number is long and parsed to eNotation + if (parsedStr.indexOf(EXP_CHAR) !== -1) { if (options.eNotation !== false) return num; else return str; } - if (((analyzeResult & IS_FLOAT) === IS_EMPTY) && (num > Number.MAX_SAFE_INTEGER || num < Number.MIN_SAFE_INTEGER)) { - return str; // If the number is out of safe integer range, return the original string + // If the number is out of safe integer range, return the original string + if (((analyzeResult & IS_FLOAT) !== IS_FLOAT) && (num > Number.MAX_SAFE_INTEGER || num < Number.MIN_SAFE_INTEGER)) { + return str; } if ((analyzeResult & IS_FLOAT) === IS_FLOAT) { @@ -100,9 +101,9 @@ export default function toNumber(str, options = {}) { const strDecimalPoint = str.indexOf(options.decimalPoint || ".") + 1; for (let i = 0; i < parsedStr.length; i++) { - if (parsedStr[parsedDecimalPoint + i] !== str[strDecimalPoint + i]) { - return str; // If the decimal part does not match, return the original string - } + if (parsedStr[parsedDecimalPoint + i] !== str[strDecimalPoint + i]) { + return str; + } } } } @@ -110,61 +111,61 @@ export default function toNumber(str, options = {}) { return num; } -const ERROR = -1; // error state -const BEGIN = 0; // initial state -const OWS = 1; // optional whitespace -const SIGN = 2; // sign -const LEADING_ZEROS = 3; // leading zeros -const INT = 4; // integer partEXP -const BEGIN_FRAC = 5; // beginning of fractional part -const FRAC = 6; // fractional part -const EXP = 7; // exponent part -const TRAILING_ZEROS = 8; // trailing zeros -const TRAILING_SPACE = 9; // trailing space -const HEX = 10; // hexadecimal state +const INVALID = ERROR; +const BEGIN = 1; +const OWS = 2; +const SIGN = 3; +const LEADING_ZEROS = 4; +const INT = 5; +const BEGIN_FRAC = 6; +const FRAC = 7; +const EXP = 8; +const TRAILING_ZEROS = 9; +const TRAILING_SPACE = 10; +const HEX = 11; /** - * @param {string} str + * @param {string} str - The string to analyze. * @param {Options} options - Options to control the parsing behavior. - * @returns {number} + * @returns {number} - A bitmask representing the analysis result of the string. */ function analyzeNumber(str, options) { let len = str.length; let state = BEGIN; let start = 0; - let length = 0 + let length = 0; let trailingZeros = 0; - let i = 0; + let pos = 0; let result = IS_EMPTY; - const ON_HEX = options.hex !== false ? HEX : ERROR; + const ON_HEX = options.hex !== false ? HEX : INVALID; const DECIMAL = options.decimalPoint || "\."; - const ON_E = options.eNotation !== false ? EXP : ERROR; + const ON_E = options.eNotation !== false ? EXP : INVALID; const NO_LEADING_ZEROS = options.leadingZeros === false; - while (i < len) { - switch (str[i]) { + while (pos < len) { + switch (str[pos]) { case " ": switch (state) { case BEGIN: - ++i; + ++pos; result |= HAS_WHITESPACE; state = OWS; continue; case OWS: case TRAILING_SPACE: - ++i; + ++pos; continue; case INT: case FRAC: - ++i; + ++pos; result |= HAS_WHITESPACE; state = TRAILING_SPACE; continue; default: - return result | HAS_ERROR; + return ERROR; } case "+": case "-": @@ -173,38 +174,38 @@ function analyzeNumber(str, options) { case OWS: case SIGN: state = LEADING_ZEROS; - start = ++i; + start = ++pos; break; case EXP: if (length === 0) { - start = ++i; + start = ++pos; ++length; continue; } default: - return result | HAS_ERROR; + return ERROR; } break; case "0": switch (state) { case BEGIN: state = LEADING_ZEROS; - start = ++i; + start = ++pos; length = 1; break; case LEADING_ZEROS: if (NO_LEADING_ZEROS && length === 1) { - return result | HAS_ERROR; + return ERROR; } ++length; ++start; - ++i; + ++pos; continue; case OWS: case SIGN: ++length; ++start; - ++i + ++pos; state = LEADING_ZEROS; continue; case BEGIN_FRAC: @@ -213,25 +214,25 @@ function analyzeNumber(str, options) { case FRAC: trailingZeros++; case INT: - ++i; + ++pos; continue; default: - return result | HAS_ERROR; + return ERROR; } break; case "x": switch (state) { case LEADING_ZEROS: if (length !== 1) { - return result | HAS_ERROR; + return ERROR; } - start = ++i; + start = ++pos; length = 0; result |= IS_HEX; state = ON_HEX; continue; default: - return result | HAS_ERROR; + return ERROR; } case "1": case "2": @@ -245,7 +246,7 @@ function analyzeNumber(str, options) { switch (state) { case LEADING_ZEROS: if (NO_LEADING_ZEROS && length === 1) { - return result | HAS_ERROR; + return ERROR; } case BEGIN: case OWS: @@ -254,7 +255,7 @@ function analyzeNumber(str, options) { case HEX: case INT: case EXP: - ++i; + ++pos; ++length; break; case BEGIN_FRAC: @@ -263,10 +264,10 @@ function analyzeNumber(str, options) { case FRAC: trailingZeros = 0; ++length; - ++i; + ++pos; break; default: - return result | HAS_ERROR; + return ERROR; } break; case "a": @@ -281,11 +282,11 @@ function analyzeNumber(str, options) { case "F": switch (state) { case HEX: - ++i; + ++pos; ++length; break; default: - return result | HAS_ERROR; + return ERROR; } break; case DECIMAL: @@ -295,12 +296,12 @@ function analyzeNumber(str, options) { case OWS: case SIGN: case INT: - start = ++i; + start = ++pos; length = 0; state = BEGIN_FRAC; break; default: - return result | HAS_ERROR; + return ERROR; } break; case "e": @@ -308,26 +309,26 @@ function analyzeNumber(str, options) { switch (state) { case LEADING_ZEROS: if (length > 1) { - return result | HAS_ERROR; + return ERROR; } case INT: case BEGIN_FRAC: case FRAC: length = 0; - start = ++i; + start = ++pos; result |= HAS_E; state = ON_E; break; case HEX: - ++i; + ++pos; ++length; break; default: - return result | HAS_ERROR; + return ERROR; } break; default: - return result | HAS_ERROR; + return ERROR; } } @@ -341,8 +342,8 @@ function analyzeNumber(str, options) { return result; case EXP: case HEX: - return length === 0 ? result | HAS_ERROR : result; + return length === 0 ? ERROR : result; default: - return result | HAS_ERROR; + return ERROR; } } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..dfdf787 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2015", + "checkJs": true, + "allowJs": true, + "declaration": true, + "outFile": "strnum.d.ts", + "emitDeclarationOnly": true + }, + "include": [ + "strnum.js" + ] +} \ No newline at end of file From 80875936abeff21874f6d1c9bb0266bd59bf6e6d Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Thu, 17 Jul 2025 01:14:33 +0200 Subject: [PATCH 12/36] improve bitmask magic --- strnum.js | 256 ++++++++++++++++++++++++++----------------------- strnum.test.js | 4 + 2 files changed, 142 insertions(+), 118 deletions(-) diff --git a/strnum.js b/strnum.js index 7ecf0c8..ae2d3b9 100644 --- a/strnum.js +++ b/strnum.js @@ -7,14 +7,6 @@ * @property {boolean} [eNotation=true] - Whether to allow scientific notation (e.g., "1e10"). */ -/** @type {Options} */ -const defaultOptions = { - hex: true, - leadingZeros: true, - decimalPoint: "\.", - eNotation: true, -}; - /** * The character used for scientific notation in numbers, based on the environment. * This is determined by checking if a large number can be represented in scientific notation. @@ -39,13 +31,6 @@ const parse_int = ((function parse_int() { }; })()); -const ERROR = 0; -const IS_EMPTY = 1 << 0; -const IS_HEX = 1 << 1; -const IS_FLOAT = 1 << 2; -const HAS_WHITESPACE = 1 << 3; -const HAS_E = 1 << 4; - /** * @template {*} T * @param {T} str - The string to convert to a number. @@ -58,7 +43,7 @@ export default function toNumber(str, options = {}) { const analyzeResult = analyzeNumber(str, options); if (options.skipLike !== undefined) { - const trimmedStr = ((analyzeResult & HAS_WHITESPACE) === HAS_WHITESPACE) + const trimmedStr = ((analyzeResult & WHITESPACE) === WHITESPACE) ? str.trim() : str; if (options.skipLike.test(trimmedStr)) { @@ -66,15 +51,15 @@ export default function toNumber(str, options = {}) { } } - if (analyzeResult === ERROR) { + if (analyzeResult === INVALID) { return str; } - if ((analyzeResult & IS_HEX) === IS_HEX) { + if ((analyzeResult & HEX) === HEX) { return parse_int(str, 16); } - if ((analyzeResult & HAS_E) === HAS_E) { + if ((analyzeResult & EXPONENT) === EXPONENT) { if (options.eNotation !== false) { return Number(str); } @@ -90,20 +75,32 @@ export default function toNumber(str, options = {}) { } // If the number is out of safe integer range, return the original string - if (((analyzeResult & IS_FLOAT) !== IS_FLOAT) && (num > Number.MAX_SAFE_INTEGER || num < Number.MIN_SAFE_INTEGER)) { + if (((analyzeResult & FLOAT) !== FLOAT) && (num > Number.MAX_SAFE_INTEGER || num < Number.MIN_SAFE_INTEGER)) { return str; } - if ((analyzeResult & IS_FLOAT) === IS_FLOAT) { + if ((analyzeResult & FLOAT) === FLOAT) { const parsedDecimalPoint = parsedStr.indexOf(".") + 1; - if ((parsedStr.length - parsedDecimalPoint) > 10) { - const strDecimalPoint = str.indexOf(options.decimalPoint || ".") + 1; + const strDecimalPoint = str.indexOf(options.decimalPoint || ".") + 1; - for (let i = 0; i < parsedStr.length; i++) { - if (parsedStr[parsedDecimalPoint + i] !== str[strDecimalPoint + i]) { + let i = 0; + const parsedFracLength = parsedStr.length - parsedDecimalPoint; + for (; i < parsedFracLength; i++) { + if (parsedStr[parsedDecimalPoint + i] !== str[strDecimalPoint + i]) { + return str; + } + } + + // ignore trailing zeros and whitespace in the fractional part + i += strDecimalPoint; + for (; i < str.length; i++) { + switch (str[i]) { + case "0": + case " ": + continue; + default: return str; - } } } } @@ -111,18 +108,41 @@ export default function toNumber(str, options = {}) { return num; } -const INVALID = ERROR; -const BEGIN = 1; -const OWS = 2; -const SIGN = 3; -const LEADING_ZEROS = 4; -const INT = 5; -const BEGIN_FRAC = 6; -const FRAC = 7; -const EXP = 8; -const TRAILING_ZEROS = 9; -const TRAILING_SPACE = 10; -const HEX = 11; +const VALID = /** @type {const} */ (0); +const INVALID = /** @type {const} */ (1); + +// Data types +const INTEGER = /** @type {const} */ (2); +const FLOAT = /** @type {const} */ (4); +const HEX = /** @type {const} */ (8); +const EXPONENT = /** @type {const} */ (16); // 'e' or 'E' + +// Special character codes +const SIGN = /** @type {const} */ (32); +const ZERO = /** @type {const} */ (64); +const WHITESPACE = /** @type {const} */ (128); + +// Positional constants +const BEGIN = /** @type {const} */ (256); +const END = /** @type {const} */ (512); + +const OWS = /** @type {const} */ (384); // WHITESPACE | BEGIN Optional whitespace +const TRAILING_WHITESPACE = /** @type {const} */ (640); // WHITESPACE | END + +const BEGIN_FLOAT_DIGITS = /** @type {const} */ (260); // FLOAT | BEGIN +const BEGIN_HEX_DIGITS = /** @type {const} */ (264); // HEX | BEGIN +const BEGIN_EXPONENT = /** @type {const} */ (272); // EXPONENT_DIGITS | BEGIN +const BEGIN_ZEROS = /** @type {const} */ (320); // BEGIN | ZEROS + +const INTEGER_DIGITS = /** @type {const} */ (2); // INTEGER +const FLOAT_DIGITS = /** @type {const} */ (4); // FLOAT +const HEX_DIGITS = /** @type {const} */ (8); // HEX +const EXPONENT_DIGITS = /** @type {const} */ (16); // EXPONENT +const ZERO_DIGITS = /** @type {const} */ (64); // ZEROS + +const INVALID_ZEROS = /** @type {const} */ (65); // ZEROS | INVALID + +/** @typedef {typeof INVALID|typeof BEGIN|typeof OWS|typeof ZERO_DIGITS|typeof BEGIN_ZEROS|typeof INVALID_ZEROS|typeof INTEGER|typeof BEGIN_FLOAT_DIGITS|typeof FLOAT_DIGITS|typeof BEGIN_EXPONENT|typeof EXPONENT_DIGITS|typeof TRAILING_WHITESPACE|typeof HEX_DIGITS|typeof BEGIN_HEX_DIGITS} State */ /** * @param {string} str - The string to analyze. @@ -132,18 +152,19 @@ const HEX = 11; function analyzeNumber(str, options) { let len = str.length; + /** @type {State} */ let state = BEGIN; let start = 0; let length = 0; let trailingZeros = 0; let pos = 0; - let result = IS_EMPTY; + let result = VALID; - const ON_HEX = options.hex !== false ? HEX : INVALID; const DECIMAL = options.decimalPoint || "\."; - const ON_E = options.eNotation !== false ? EXP : INVALID; - const NO_LEADING_ZEROS = options.leadingZeros === false; + const ON_HEX = options.hex !== false ? BEGIN_HEX_DIGITS : INVALID; + const ON_E = options.eNotation !== false ? BEGIN_EXPONENT : INVALID; + const ON_LEADING_ZEROS = options.leadingZeros === false ? INVALID_ZEROS : BEGIN_ZEROS; while (pos < len) { switch (str[pos]) { @@ -151,88 +172,80 @@ function analyzeNumber(str, options) { switch (state) { case BEGIN: ++pos; - result |= HAS_WHITESPACE; + result |= WHITESPACE; state = OWS; continue; + case TRAILING_WHITESPACE: case OWS: - case TRAILING_SPACE: ++pos; continue; - case INT: - case FRAC: + case INTEGER_DIGITS: + case FLOAT_DIGITS: ++pos; - result |= HAS_WHITESPACE; - state = TRAILING_SPACE; + result |= WHITESPACE; + state = TRAILING_WHITESPACE; continue; default: - return ERROR; + return INVALID; } case "+": case "-": switch (state) { case BEGIN: case OWS: - case SIGN: - state = LEADING_ZEROS; + result |= SIGN; + state = ZERO_DIGITS; start = ++pos; break; - case EXP: + case BEGIN_EXPONENT: if (length === 0) { start = ++pos; ++length; + state = EXPONENT_DIGITS; continue; } default: - return ERROR; + return INVALID; } break; case "0": switch (state) { - case BEGIN: - state = LEADING_ZEROS; - start = ++pos; - length = 1; - break; - case LEADING_ZEROS: - if (NO_LEADING_ZEROS && length === 1) { - return ERROR; - } - ++length; - ++start; - ++pos; - continue; + case INVALID_ZEROS: + return INVALID; case OWS: - case SIGN: + case BEGIN: + case ZERO_DIGITS: + state = ON_LEADING_ZEROS; + case BEGIN_ZEROS: ++length; ++start; ++pos; - state = LEADING_ZEROS; continue; - case BEGIN_FRAC: - result |= IS_FLOAT; - state = FRAC; - case FRAC: + case BEGIN_FLOAT_DIGITS: + state = FLOAT_DIGITS; + case FLOAT_DIGITS: trailingZeros++; - case INT: + case INTEGER_DIGITS: ++pos; continue; default: - return ERROR; + return INVALID; } break; case "x": switch (state) { - case LEADING_ZEROS: + case INVALID_ZEROS: + case BEGIN_ZEROS: if (length !== 1) { - return ERROR; + return INVALID; } start = ++pos; length = 0; - result |= IS_HEX; + result |= HEX; state = ON_HEX; continue; default: - return ERROR; + return INVALID; } case "1": case "2": @@ -244,30 +257,34 @@ function analyzeNumber(str, options) { case "8": case "9": switch (state) { - case LEADING_ZEROS: - if (NO_LEADING_ZEROS && length === 1) { - return ERROR; - } + case ZERO_DIGITS: + case BEGIN_ZEROS: case BEGIN: case OWS: - case SIGN: - state = INT; - case HEX: - case INT: - case EXP: + state = INTEGER; + case INTEGER_DIGITS: + case EXPONENT_DIGITS: + case HEX_DIGITS: ++pos; ++length; break; - case BEGIN_FRAC: - result |= IS_FLOAT; - state = FRAC; - case FRAC: + case BEGIN_HEX_DIGITS: + result |= HEX; + state = HEX_DIGITS; + case BEGIN_EXPONENT: + ++pos; + ++length; + state = EXPONENT_DIGITS; + case BEGIN_FLOAT_DIGITS: + result |= FLOAT; + state = FLOAT_DIGITS; + case FLOAT_DIGITS: trailingZeros = 0; ++length; ++pos; break; default: - return ERROR; + return INVALID; } break; case "a": @@ -281,69 +298,72 @@ function analyzeNumber(str, options) { case "D": case "F": switch (state) { - case HEX: + case BEGIN_HEX_DIGITS: + state = HEX_DIGITS; + case HEX_DIGITS: ++pos; ++length; break; default: - return ERROR; + return INVALID; } break; case DECIMAL: switch (state) { case BEGIN: - case LEADING_ZEROS: + case ZERO_DIGITS: + case INVALID_ZEROS: + case BEGIN_ZEROS: case OWS: - case SIGN: - case INT: + case INTEGER_DIGITS: start = ++pos; length = 0; - state = BEGIN_FRAC; + state = BEGIN_FLOAT_DIGITS; break; default: - return ERROR; + return INVALID; } break; case "e": case "E": switch (state) { - case LEADING_ZEROS: + case BEGIN_ZEROS: if (length > 1) { - return ERROR; + return INVALID; } - case INT: - case BEGIN_FRAC: - case FRAC: + case INTEGER_DIGITS: + case BEGIN_FLOAT_DIGITS: + case FLOAT_DIGITS: length = 0; start = ++pos; - result |= HAS_E; + result |= EXPONENT; state = ON_E; break; - case HEX: + case HEX_DIGITS: ++pos; ++length; break; default: - return ERROR; + return INVALID; } break; default: - return ERROR; + return INVALID; } } switch (state) { - case LEADING_ZEROS: - case INT: - case BEGIN_FRAC: - case FRAC: - case TRAILING_ZEROS: - case TRAILING_SPACE: + case INVALID_ZEROS: + case BEGIN_ZEROS: + case INTEGER_DIGITS: + case BEGIN_FLOAT_DIGITS: + case FLOAT_DIGITS: + case TRAILING_WHITESPACE: return result; - case EXP: - case HEX: - return length === 0 ? ERROR : result; + case EXPONENT_DIGITS: + case HEX_DIGITS: + return length === 0 ? INVALID : result; default: - return ERROR; + return INVALID; } } diff --git a/strnum.test.js b/strnum.test.js index f333801..ca2fcbe 100644 --- a/strnum.test.js +++ b/strnum.test.js @@ -31,6 +31,7 @@ describe("Should convert all the valid numeric strings to number", () => { }) it("should not parse strings with 0x embedded", () => { expect(toNumber("0xzz")).toEqual("0xzz"); + expect(toNumber("0x")).toEqual("0x"); expect(toNumber("iweraf0x123qwerqwer")).toEqual("iweraf0x123qwerqwer"); expect(toNumber("1230x55")).toEqual("1230x55"); expect(toNumber("JVBERi0xLjMNCiXi48")).toEqual("JVBERi0xLjMNCiXi48"); @@ -110,6 +111,7 @@ describe("Should convert all the valid numeric strings to number", () => { expect(toNumber("20211201030005811824") ).toEqual("20211201030005811824"); expect(toNumber("20.211201030005811824") ).toEqual("20.211201030005811824"); expect(toNumber("0.211201030005811824") ).toEqual("0.211201030005811824"); + expect(toNumber("0.21120103000500000000000 ") ).toEqual(0.211201030005); }); it("scientific notation", () => { expect(toNumber("01.0e2" , { leadingZeros : false})).toEqual("01.0e2"); @@ -118,6 +120,8 @@ describe("Should convert all the valid numeric strings to number", () => { expect(toNumber("-01.0e2") ).toEqual(-100); expect(toNumber("1.0e2") ).toEqual(100); + expect(toNumber("1.0e2 ") ).toEqual(100); + expect(toNumber("-1.0e2") ).toEqual(-100); expect(toNumber("1.0e-2")).toEqual(0.01); From 4456d138191017e027fb938c2c51d70beb185141 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Thu, 17 Jul 2025 02:44:58 +0200 Subject: [PATCH 13/36] simplify --- strnum.js | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/strnum.js b/strnum.js index ae2d3b9..b3f612d 100644 --- a/strnum.js +++ b/strnum.js @@ -13,11 +13,7 @@ * @type {"e"|"E"} * @constant */ -const EXP_CHAR = (function returnExpChar() { - const bigNumber = 1e1000; - const str = String(bigNumber); - return str.indexOf("e") === -1 ? "e" : "E"; -})(); +const EXP_CHAR = String(1e100).indexOf("e") !== -1 ? "e" : "E"; /** * @type {(string: string, radix: 10|16) => number} @@ -42,6 +38,10 @@ export default function toNumber(str, options = {}) { const analyzeResult = analyzeNumber(str, options); + if (analyzeResult === INVALID) { + return str; + } + if (options.skipLike !== undefined) { const trimmedStr = ((analyzeResult & WHITESPACE) === WHITESPACE) ? str.trim() @@ -51,10 +51,6 @@ export default function toNumber(str, options = {}) { } } - if (analyzeResult === INVALID) { - return str; - } - if ((analyzeResult & HEX) === HEX) { return parse_int(str, 16); } @@ -175,8 +171,8 @@ function analyzeNumber(str, options) { result |= WHITESPACE; state = OWS; continue; - case TRAILING_WHITESPACE: case OWS: + case TRAILING_WHITESPACE: ++pos; continue; case INTEGER_DIGITS: @@ -231,7 +227,6 @@ function analyzeNumber(str, options) { default: return INVALID; } - break; case "x": switch (state) { case INVALID_ZEROS: From 882021dfd2f52117dfa9e11c24d5811b014949d1 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Thu, 17 Jul 2025 20:10:24 +0200 Subject: [PATCH 14/36] reduce bs --- strnum.js | 144 +++++++++++++++++++++---------------------------- strnum.test.js | 4 ++ 2 files changed, 66 insertions(+), 82 deletions(-) diff --git a/strnum.js b/strnum.js index b3f612d..a394d37 100644 --- a/strnum.js +++ b/strnum.js @@ -125,20 +125,17 @@ const END = /** @type {const} */ (512); const OWS = /** @type {const} */ (384); // WHITESPACE | BEGIN Optional whitespace const TRAILING_WHITESPACE = /** @type {const} */ (640); // WHITESPACE | END +const BEGIN_INTEGER_DIGITS = /** @type {const} */ (258); // INTEGER | BEGIN const BEGIN_FLOAT_DIGITS = /** @type {const} */ (260); // FLOAT | BEGIN -const BEGIN_HEX_DIGITS = /** @type {const} */ (264); // HEX | BEGIN +const BEGIN_HEX = /** @type {const} */ (264); // HEX | BEGIN const BEGIN_EXPONENT = /** @type {const} */ (272); // EXPONENT_DIGITS | BEGIN const BEGIN_ZEROS = /** @type {const} */ (320); // BEGIN | ZEROS -const INTEGER_DIGITS = /** @type {const} */ (2); // INTEGER -const FLOAT_DIGITS = /** @type {const} */ (4); // FLOAT -const HEX_DIGITS = /** @type {const} */ (8); // HEX -const EXPONENT_DIGITS = /** @type {const} */ (16); // EXPONENT const ZERO_DIGITS = /** @type {const} */ (64); // ZEROS const INVALID_ZEROS = /** @type {const} */ (65); // ZEROS | INVALID -/** @typedef {typeof INVALID|typeof BEGIN|typeof OWS|typeof ZERO_DIGITS|typeof BEGIN_ZEROS|typeof INVALID_ZEROS|typeof INTEGER|typeof BEGIN_FLOAT_DIGITS|typeof FLOAT_DIGITS|typeof BEGIN_EXPONENT|typeof EXPONENT_DIGITS|typeof TRAILING_WHITESPACE|typeof HEX_DIGITS|typeof BEGIN_HEX_DIGITS} State */ +/** @typedef {typeof INVALID|typeof BEGIN|typeof OWS|typeof ZERO_DIGITS|typeof BEGIN_ZEROS|typeof INVALID_ZEROS|typeof INTEGER|typeof BEGIN_FLOAT_DIGITS|typeof FLOAT|typeof BEGIN_EXPONENT|typeof EXPONENT|typeof TRAILING_WHITESPACE|typeof HEX|typeof BEGIN_HEX} State */ /** * @param {string} str - The string to analyze. @@ -150,34 +147,30 @@ function analyzeNumber(str, options) { /** @type {State} */ let state = BEGIN; - let start = 0; let length = 0; - let trailingZeros = 0; - let pos = 0; + let pos = -1; let result = VALID; const DECIMAL = options.decimalPoint || "\."; - const ON_HEX = options.hex !== false ? BEGIN_HEX_DIGITS : INVALID; + const ON_HEX = options.hex !== false ? BEGIN_HEX : INVALID; const ON_E = options.eNotation !== false ? BEGIN_EXPONENT : INVALID; const ON_LEADING_ZEROS = options.leadingZeros === false ? INVALID_ZEROS : BEGIN_ZEROS; - while (pos < len) { + while (++pos < len) { switch (str[pos]) { case " ": switch (state) { case BEGIN: - ++pos; result |= WHITESPACE; state = OWS; - continue; case OWS: case TRAILING_WHITESPACE: - ++pos; continue; - case INTEGER_DIGITS: - case FLOAT_DIGITS: - ++pos; + case HEX: + case EXPONENT: + case INTEGER: + case FLOAT: result |= WHITESPACE; state = TRAILING_WHITESPACE; continue; @@ -191,21 +184,21 @@ function analyzeNumber(str, options) { case OWS: result |= SIGN; state = ZERO_DIGITS; - start = ++pos; - break; + continue; case BEGIN_EXPONENT: - if (length === 0) { - start = ++pos; - ++length; - state = EXPONENT_DIGITS; - continue; - } + state = EXPONENT; + continue; default: return INVALID; } - break; case "0": switch (state) { + case FLOAT: + ++length; + case EXPONENT: + case HEX: + case INTEGER: + continue; case INVALID_ZEROS: return INVALID; case OWS: @@ -214,29 +207,28 @@ function analyzeNumber(str, options) { state = ON_LEADING_ZEROS; case BEGIN_ZEROS: ++length; - ++start; - ++pos; + continue; + case BEGIN_EXPONENT: + result |= EXPONENT; + state = EXPONENT; continue; case BEGIN_FLOAT_DIGITS: - state = FLOAT_DIGITS; - case FLOAT_DIGITS: - trailingZeros++; - case INTEGER_DIGITS: - ++pos; + state = FLOAT; + continue; + case BEGIN_HEX: + result |= HEX; + state = HEX; continue; default: return INVALID; } case "x": switch (state) { - case INVALID_ZEROS: case BEGIN_ZEROS: if (length !== 1) { return INVALID; } - start = ++pos; - length = 0; - result |= HEX; + case INVALID_ZEROS: state = ON_HEX; continue; default: @@ -252,36 +244,33 @@ function analyzeNumber(str, options) { case "8": case "9": switch (state) { + case FLOAT: + length = 0; + case EXPONENT: + case HEX: + case INTEGER: + continue; case ZERO_DIGITS: case BEGIN_ZEROS: case BEGIN: case OWS: state = INTEGER; - case INTEGER_DIGITS: - case EXPONENT_DIGITS: - case HEX_DIGITS: - ++pos; - ++length; - break; - case BEGIN_HEX_DIGITS: + continue; + case BEGIN_HEX: result |= HEX; - state = HEX_DIGITS; + state = HEX; + continue; case BEGIN_EXPONENT: - ++pos; - ++length; - state = EXPONENT_DIGITS; + result |= EXPONENT; + state = EXPONENT; + continue; case BEGIN_FLOAT_DIGITS: result |= FLOAT; - state = FLOAT_DIGITS; - case FLOAT_DIGITS: - trailingZeros = 0; - ++length; - ++pos; - break; + state = FLOAT; + continue; default: return INVALID; } - break; case "a": case "b": case "c": @@ -293,16 +282,14 @@ function analyzeNumber(str, options) { case "D": case "F": switch (state) { - case BEGIN_HEX_DIGITS: - state = HEX_DIGITS; - case HEX_DIGITS: - ++pos; - ++length; - break; + case BEGIN_HEX: + result |= HEX; + state = HEX; + case HEX: + continue; default: return INVALID; } - break; case DECIMAL: switch (state) { case BEGIN: @@ -310,38 +297,30 @@ function analyzeNumber(str, options) { case INVALID_ZEROS: case BEGIN_ZEROS: case OWS: - case INTEGER_DIGITS: - start = ++pos; - length = 0; + case INTEGER: state = BEGIN_FLOAT_DIGITS; - break; + continue; default: return INVALID; } - break; case "e": case "E": switch (state) { + case HEX: + continue; case BEGIN_ZEROS: if (length > 1) { return INVALID; } - case INTEGER_DIGITS: + case INTEGER: case BEGIN_FLOAT_DIGITS: - case FLOAT_DIGITS: - length = 0; - start = ++pos; + case FLOAT: result |= EXPONENT; state = ON_E; - break; - case HEX_DIGITS: - ++pos; - ++length; - break; + continue; default: return INVALID; } - break; default: return INVALID; } @@ -350,15 +329,16 @@ function analyzeNumber(str, options) { switch (state) { case INVALID_ZEROS: case BEGIN_ZEROS: - case INTEGER_DIGITS: + case INTEGER: case BEGIN_FLOAT_DIGITS: - case FLOAT_DIGITS: + case FLOAT: case TRAILING_WHITESPACE: + case EXPONENT: + case HEX: return result; - case EXPONENT_DIGITS: - case HEX_DIGITS: - return length === 0 ? INVALID : result; default: return INVALID; } } + +console.log(toNumber('1.0e2 ')) \ No newline at end of file diff --git a/strnum.test.js b/strnum.test.js index ca2fcbe..280e77f 100644 --- a/strnum.test.js +++ b/strnum.test.js @@ -24,6 +24,7 @@ describe("Should convert all the valid numeric strings to number", () => { it("should parse hexadecimal values", () => { expect(toNumber("0x2f")).toEqual(47); expect(toNumber("-0x2f")).toEqual(-47); + expect(toNumber("0x0", { hex : true})).toEqual(0); expect(toNumber("0x2f", { hex : true})).toEqual(47); expect(toNumber("-0x2f", { hex : true})).toEqual(-47); expect(toNumber("0x2f", { hex : false})).toEqual("0x2f"); @@ -121,6 +122,8 @@ describe("Should convert all the valid numeric strings to number", () => { expect(toNumber("1.0e2") ).toEqual(100); expect(toNumber("1.0e2 ") ).toEqual(100); + expect(toNumber("1.0e02") ).toEqual(100); + expect(toNumber("1.0e002") ).toEqual(100); expect(toNumber("-1.0e2") ).toEqual(-100); expect(toNumber("1.0e-2")).toEqual(0.01); @@ -131,6 +134,7 @@ describe("Should convert all the valid numeric strings to number", () => { expect(toNumber("1e-2")).toEqual(0.01); expect(toNumber("1e+2")).toEqual(100); expect(toNumber("1.e+2")).toEqual(100); + expect(toNumber("1e.2")).toEqual("1e.2"); }); it("scientific notation with upper E", () => { From f5e482efb6fae00b4f957a91744eab35c496a96c Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Sun, 20 Jul 2025 22:51:57 +0200 Subject: [PATCH 15/36] reduce --- strnum.js | 52 ++++++++++++++++------------------------------------ 1 file changed, 16 insertions(+), 36 deletions(-) diff --git a/strnum.js b/strnum.js index a394d37..c627b2b 100644 --- a/strnum.js +++ b/strnum.js @@ -164,8 +164,6 @@ function analyzeNumber(str, options) { case BEGIN: result |= WHITESPACE; state = OWS; - case OWS: - case TRAILING_WHITESPACE: continue; case HEX: case EXPONENT: @@ -173,6 +171,8 @@ function analyzeNumber(str, options) { case FLOAT: result |= WHITESPACE; state = TRAILING_WHITESPACE; + case OWS: + case TRAILING_WHITESPACE: continue; default: return INVALID; @@ -184,21 +184,25 @@ function analyzeNumber(str, options) { case OWS: result |= SIGN; state = ZERO_DIGITS; - continue; case BEGIN_EXPONENT: - state = EXPONENT; continue; default: return INVALID; } - case "0": + case "x": switch (state) { - case FLOAT: - ++length; - case EXPONENT: - case HEX: - case INTEGER: + case BEGIN_ZEROS: + if (length !== 1) { + return INVALID; + } + case INVALID_ZEROS: + state = ON_HEX; continue; + default: + return INVALID; + } + case "0": + switch (state) { case INVALID_ZEROS: return INVALID; case OWS: @@ -206,33 +210,10 @@ function analyzeNumber(str, options) { case ZERO_DIGITS: state = ON_LEADING_ZEROS; case BEGIN_ZEROS: + case FLOAT: ++length; - continue; - case BEGIN_EXPONENT: - result |= EXPONENT; - state = EXPONENT; - continue; case BEGIN_FLOAT_DIGITS: - state = FLOAT; - continue; - case BEGIN_HEX: - result |= HEX; - state = HEX; - continue; - default: - return INVALID; - } - case "x": - switch (state) { - case BEGIN_ZEROS: - if (length !== 1) { - return INVALID; - } - case INVALID_ZEROS: - state = ON_HEX; continue; - default: - return INVALID; } case "1": case "2": @@ -306,8 +287,6 @@ function analyzeNumber(str, options) { case "e": case "E": switch (state) { - case HEX: - continue; case BEGIN_ZEROS: if (length > 1) { return INVALID; @@ -317,6 +296,7 @@ function analyzeNumber(str, options) { case FLOAT: result |= EXPONENT; state = ON_E; + case HEX: continue; default: return INVALID; From 5963d3d8946b388da184adeba7d8ab51a7494686 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Sun, 20 Jul 2025 22:57:32 +0200 Subject: [PATCH 16/36] exponent sign repetition --- strnum.js | 7 ++++++- strnum.test.js | 6 ++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/strnum.js b/strnum.js index c627b2b..4a741a6 100644 --- a/strnum.js +++ b/strnum.js @@ -131,11 +131,13 @@ const BEGIN_HEX = /** @type {const} */ (264); // HEX | BEGIN const BEGIN_EXPONENT = /** @type {const} */ (272); // EXPONENT_DIGITS | BEGIN const BEGIN_ZEROS = /** @type {const} */ (320); // BEGIN | ZEROS +const EXPONENT_SIGN = /** @type {const} */ (48); // EXPONENT | SIGN + const ZERO_DIGITS = /** @type {const} */ (64); // ZEROS const INVALID_ZEROS = /** @type {const} */ (65); // ZEROS | INVALID -/** @typedef {typeof INVALID|typeof BEGIN|typeof OWS|typeof ZERO_DIGITS|typeof BEGIN_ZEROS|typeof INVALID_ZEROS|typeof INTEGER|typeof BEGIN_FLOAT_DIGITS|typeof FLOAT|typeof BEGIN_EXPONENT|typeof EXPONENT|typeof TRAILING_WHITESPACE|typeof HEX|typeof BEGIN_HEX} State */ +/** @typedef {typeof INVALID|typeof EXPONENT_SIGN|typeof BEGIN|typeof OWS|typeof ZERO_DIGITS|typeof BEGIN_ZEROS|typeof INVALID_ZEROS|typeof INTEGER|typeof BEGIN_FLOAT_DIGITS|typeof FLOAT|typeof BEGIN_EXPONENT|typeof EXPONENT|typeof TRAILING_WHITESPACE|typeof HEX|typeof BEGIN_HEX} State */ /** * @param {string} str - The string to analyze. @@ -184,7 +186,9 @@ function analyzeNumber(str, options) { case OWS: result |= SIGN; state = ZERO_DIGITS; + continue; case BEGIN_EXPONENT: + state = EXPONENT_SIGN; continue; default: return INVALID; @@ -242,6 +246,7 @@ function analyzeNumber(str, options) { state = HEX; continue; case BEGIN_EXPONENT: + case EXPONENT_SIGN: result |= EXPONENT; state = EXPONENT; continue; diff --git a/strnum.test.js b/strnum.test.js index 280e77f..cc38469 100644 --- a/strnum.test.js +++ b/strnum.test.js @@ -134,6 +134,12 @@ describe("Should convert all the valid numeric strings to number", () => { expect(toNumber("1e-2")).toEqual(0.01); expect(toNumber("1e+2")).toEqual(100); expect(toNumber("1.e+2")).toEqual(100); + expect(toNumber("1.e++2")).toEqual("1.e++2"); + expect(toNumber("1.e+-2")).toEqual("1.e+-2"); + expect(toNumber("1.e-+2")).toEqual("1.e-+2"); + expect(toNumber("1e++2")).toEqual("1e++2"); + expect(toNumber("1e+-2")).toEqual("1e+-2"); + expect(toNumber("1e-+2")).toEqual("1e-+2"); expect(toNumber("1e.2")).toEqual("1e.2"); }); From bcd8a4277453d5a643e98e99c00139955bac2593 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Sun, 20 Jul 2025 23:06:57 +0200 Subject: [PATCH 17/36] ensure EXP_CHAR is always e or E --- strnum.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/strnum.js b/strnum.js index 4a741a6..29d7322 100644 --- a/strnum.js +++ b/strnum.js @@ -13,7 +13,16 @@ * @type {"e"|"E"} * @constant */ -const EXP_CHAR = String(1e100).indexOf("e") !== -1 ? "e" : "E"; +const EXP_CHAR = function () { + const bigNumberAsString = String(1e100) + if (bigNumberAsString.indexOf("e") !== -1) { + return "e"; + } else if (bigNumberAsString.indexOf("E") !== -1) { + return "E"; + } else { + throw new Error("Cannot determine scientific notation character"); + } +}(); /** * @type {(string: string, radix: 10|16) => number} @@ -279,10 +288,10 @@ function analyzeNumber(str, options) { case DECIMAL: switch (state) { case BEGIN: + case OWS: case ZERO_DIGITS: case INVALID_ZEROS: case BEGIN_ZEROS: - case OWS: case INTEGER: state = BEGIN_FLOAT_DIGITS; continue; From 272539f69d1a2350ee98b4f4538f3384750756b5 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Mon, 21 Jul 2025 02:22:27 +0200 Subject: [PATCH 18/36] more --- analyze-number.test.js | 6 + strnum.d.ts | 2 + strnum.js | 306 ++++++++++++++++++++++++++++++----------- 3 files changed, 233 insertions(+), 81 deletions(-) create mode 100644 analyze-number.test.js diff --git a/analyze-number.test.js b/analyze-number.test.js new file mode 100644 index 0000000..f083d24 --- /dev/null +++ b/analyze-number.test.js @@ -0,0 +1,6 @@ +import { analyzeNumber } from "./strnum.js"; + +describe("analyzeNumber", () => { + it("", () => { + }); +}) \ No newline at end of file diff --git a/strnum.d.ts b/strnum.d.ts index 267647c..fb585b9 100644 --- a/strnum.d.ts +++ b/strnum.d.ts @@ -28,4 +28,6 @@ declare module "strnum" { */ eNotation?: boolean; }; + + export function analyzeNumber(str: string, options?: Options): number; } diff --git a/strnum.js b/strnum.js index 29d7322..0eb51a8 100644 --- a/strnum.js +++ b/strnum.js @@ -14,19 +14,17 @@ * @constant */ const EXP_CHAR = function () { - const bigNumberAsString = String(1e100) - if (bigNumberAsString.indexOf("e") !== -1) { - return "e"; - } else if (bigNumberAsString.indexOf("E") !== -1) { - return "E"; - } else { - throw new Error("Cannot determine scientific notation character"); - } + const bigNumberAsString = String(1e100) + if (bigNumberAsString.indexOf("e") !== -1) { + return "e"; + } else if (bigNumberAsString.indexOf("E") !== -1) { + return "E"; + } else { + throw new Error("Cannot determine scientific notation character"); + } }(); -/** - * @type {(string: string, radix: 10|16) => number} - */ +/** @type {(string: string, radix: 10|16) => number} */ const parse_int = ((function parse_int() { if (parseInt) return parseInt; else if (Number.parseInt) return Number.parseInt; @@ -47,7 +45,7 @@ export default function toNumber(str, options = {}) { const analyzeResult = analyzeNumber(str, options); - if (analyzeResult === INVALID) { + if (analyzeResult === NOT_A_NUMBER) { return str; } @@ -64,14 +62,14 @@ export default function toNumber(str, options = {}) { return parse_int(str, 16); } - if ((analyzeResult & EXPONENT) === EXPONENT) { + if ((analyzeResult & EXPONENT_INDICATOR) === EXPONENT_INDICATOR) { if (options.eNotation !== false) { return Number(str); } return str; } - const num = Number(str); + const num = (analyzeResult & INTEGER) === INTEGER ? parse_int(str, 10) : Number(str); const parsedStr = String(num); if (parsedStr.indexOf(EXP_CHAR) !== -1) { @@ -113,47 +111,100 @@ export default function toNumber(str, options = {}) { return num; } -const VALID = /** @type {const} */ (0); -const INVALID = /** @type {const} */ (1); +const NUMBER = /** @type {const} */ assertBitmask(0, 0); +const NOT_A_NUMBER = /** @type {const} */ assertBitmask(1, 1 << 0); + +const BINARY = /** @type {const} */ assertBitmask(2, 1 << 1); +const DECIMAL = /** @type {const} */ assertBitmask(4, 1 << 2); +const OCTAL = /** @type {const} */ assertBitmask(8, 1 << 3); +const HEX = /** @type {const} */ assertBitmask(16, 1 << 4); -// Data types -const INTEGER = /** @type {const} */ (2); -const FLOAT = /** @type {const} */ (4); -const HEX = /** @type {const} */ (8); -const EXPONENT = /** @type {const} */ (16); // 'e' or 'E' +const FLOAT = /** @type {const} */ assertBitmask(32, 1 << 5); +const INTEGER = /** @type {const} */ assertBitmask(64, 1 << 6); +const BIGINT = /** @type {const} */ assertBitmask(2112, INTEGER | 1 << 11); // Special character codes -const SIGN = /** @type {const} */ (32); -const ZERO = /** @type {const} */ (64); -const WHITESPACE = /** @type {const} */ (128); +const WHITESPACE = /** @type {const} */ assertBitmask(128, 1 << 7); +const ZERO = /** @type {const} */ assertBitmask(256, 1 << 8); +const SIGN = /** @type {const} */ assertBitmask(512, 1 << 9); +const EXPONENT_INDICATOR = /** @type {const} */ assertBitmask(1024, 1 << 10); // 'e' or 'E' +const BIGINT_LITERAL_SUFFIX = /** @type {const} */ assertBitmask(2048, 1 << 11); // 'n' for BigInt + // Positional constants -const BEGIN = /** @type {const} */ (256); -const END = /** @type {const} */ (512); +const BEGIN = /** @type {const} */ assertBitmask(2048, 1 << 11); +const END = /** @type {const} */ assertBitmask(4096, 1 << 12); + +const OWS = /** @type {const} */ assertBitmask(2176, WHITESPACE | BEGIN); +const TRAILING_WHITESPACE = /** @type {const} */ assertBitmask(4224, WHITESPACE | END); -const OWS = /** @type {const} */ (384); // WHITESPACE | BEGIN Optional whitespace -const TRAILING_WHITESPACE = /** @type {const} */ (640); // WHITESPACE | END +const BEGIN_INTEGER_DIGITS = /** @type {const} */ assertBitmask(2052, DECIMAL | BEGIN); +const BEGIN_FRAC_DIGITS = /** @type {const} */ assertBitmask(2080, FLOAT | BEGIN); +const BEGIN_HEX = /** @type {const} */ assertBitmask(2064, HEX | BEGIN); +const BEGIN_OCTAL = /** @type {const} */ assertBitmask(2056, OCTAL | BEGIN); +const BEGIN_BINARY = /** @type {const} */ assertBitmask(2050, BINARY | BEGIN); +const BEGIN_ZEROS = /** @type {const} */ assertBitmask(2304, BEGIN | ZERO); -const BEGIN_INTEGER_DIGITS = /** @type {const} */ (258); // INTEGER | BEGIN -const BEGIN_FLOAT_DIGITS = /** @type {const} */ (260); // FLOAT | BEGIN -const BEGIN_HEX = /** @type {const} */ (264); // HEX | BEGIN -const BEGIN_EXPONENT = /** @type {const} */ (272); // EXPONENT_DIGITS | BEGIN -const BEGIN_ZEROS = /** @type {const} */ (320); // BEGIN | ZEROS +const BEGIN_EXPONENT = /** @type {const} */ assertBitmask(3072, EXPONENT_INDICATOR | BEGIN); +const EXPONENT_SIGN = /** @type {const} */ assertBitmask(1536, EXPONENT_INDICATOR | SIGN); +const EXPONENT_INTEGER = /** @type {const} */ assertBitmask(1088, EXPONENT_INDICATOR | INTEGER); -const EXPONENT_SIGN = /** @type {const} */ (48); // EXPONENT | SIGN +const ZERO_DIGITS = /** @type {const} */ assertBitmask(256, ZERO); -const ZERO_DIGITS = /** @type {const} */ (64); // ZEROS +const INVALID_ZEROS = /** @type {const} */ assertBitmask(257, ZERO | NOT_A_NUMBER); -const INVALID_ZEROS = /** @type {const} */ (65); // ZEROS | INVALID +/** + * @typedef {typeof NUMBER | + * typeof NOT_A_NUMBER | + * typeof BINARY | + * typeof DECIMAL | + * typeof OCTAL | + * typeof HEX | + * typeof FLOAT | + * typeof INTEGER | + * typeof BIGINT | + * typeof BIGINT_LITERAL_SUFFIX | + * typeof ZERO | + * typeof WHITESPACE | + * typeof BEGIN | + * typeof END | + * typeof OWS | + * typeof TRAILING_WHITESPACE | + * typeof BEGIN_INTEGER_DIGITS | + * typeof BEGIN_FRAC_DIGITS | + * typeof BEGIN_BINARY | + * typeof BEGIN_HEX | + * typeof BEGIN_OCTAL | + * typeof BEGIN_EXPONENT | + * typeof BEGIN_ZEROS | + * typeof ZERO_DIGITS | + * typeof INVALID_ZEROS | + * typeof SIGN | + * typeof EXPONENT_INDICATOR | + * typeof EXPONENT_SIGN | + * typeof EXPONENT_INTEGER + * } State + */ -/** @typedef {typeof INVALID|typeof EXPONENT_SIGN|typeof BEGIN|typeof OWS|typeof ZERO_DIGITS|typeof BEGIN_ZEROS|typeof INVALID_ZEROS|typeof INTEGER|typeof BEGIN_FLOAT_DIGITS|typeof FLOAT|typeof BEGIN_EXPONENT|typeof EXPONENT|typeof TRAILING_WHITESPACE|typeof HEX|typeof BEGIN_HEX} State */ +/** + * @template {number} T + * @param {T} value + * @param {number} bitmask + * @returns {T} - Returns the value if it matches the bitmask, otherwise throws an error. + */ +function assertBitmask(value, bitmask) { + if (value !== bitmask) { + throw new Error(`Expected bitmask ${bitmask}, but got ${value}`); + } + return value; +} /** * @param {string} str - The string to analyze. * @param {Options} options - Options to control the parsing behavior. * @returns {number} - A bitmask representing the analysis result of the string. */ -function analyzeNumber(str, options) { +export function analyzeNumber(str, options) { let len = str.length; /** @type {State} */ @@ -161,24 +212,53 @@ function analyzeNumber(str, options) { let length = 0; let pos = -1; - let result = VALID; + let result = NUMBER; - const DECIMAL = options.decimalPoint || "\."; - const ON_HEX = options.hex !== false ? BEGIN_HEX : INVALID; - const ON_E = options.eNotation !== false ? BEGIN_EXPONENT : INVALID; + const DECIMAL_POINT = options.decimalPoint || "\."; + const ON_HEX = options.hex !== false ? BEGIN_HEX : NOT_A_NUMBER; + const ON_E = options.eNotation !== false ? BEGIN_EXPONENT : NOT_A_NUMBER; + const ON_BINARY = BEGIN_BINARY; + const ON_OCTAL = BEGIN_OCTAL; const ON_LEADING_ZEROS = options.leadingZeros === false ? INVALID_ZEROS : BEGIN_ZEROS; while (++pos < len) { switch (str[pos]) { + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#white_space case " ": + case "\t": + case "\v": + case "\f": + case "\r": + case "\n": + case "\ufeff": // Unicode line separator + // https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5Cp%7BGeneral_Category%3DSpace_Separator%7D + case "\u00A0": // Non-breaking space + case "\u1680": // Ogham space mark + case "\u2000": // En quad + case "\u2001": // Em quad + case "\u2002": // En space + case "\u2003": // Em space + case "\u2004": // Three-per-em space + case "\u2005": // Four-per-em space + case "\u2006": // Six-per-em space + case "\u2007": // Figure space + case "\u2008": // Punctuation space + case "\u2009": // Thin space + case "\u200A": // Hair space + case "\u202F": // Narrow no-break space + case "\u205F": // Medium mathematical space + case "\u3000": // Ideographic space switch (state) { case BEGIN: result |= WHITESPACE; state = OWS; continue; + case BINARY: + case OCTAL: + case DECIMAL: case HEX: - case EXPONENT: - case INTEGER: + case EXPONENT_INTEGER: + case BIGINT_LITERAL_SUFFIX: case FLOAT: result |= WHITESPACE; state = TRAILING_WHITESPACE; @@ -186,7 +266,7 @@ function analyzeNumber(str, options) { case TRAILING_WHITESPACE: continue; default: - return INVALID; + return NOT_A_NUMBER; } case "+": case "-": @@ -194,61 +274,95 @@ function analyzeNumber(str, options) { case BEGIN: case OWS: result |= SIGN; - state = ZERO_DIGITS; + state = SIGN; continue; case BEGIN_EXPONENT: state = EXPONENT_SIGN; continue; default: - return INVALID; + return NOT_A_NUMBER; + } + case "o": + switch (state) { + case BEGIN_ZEROS: + if (length !== 1) { + return NOT_A_NUMBER; + } + case INVALID_ZEROS: + state = ON_OCTAL; + continue; + default: + return NOT_A_NUMBER; } case "x": switch (state) { case BEGIN_ZEROS: if (length !== 1) { - return INVALID; + return NOT_A_NUMBER; } case INVALID_ZEROS: state = ON_HEX; continue; default: - return INVALID; + return NOT_A_NUMBER; } case "0": switch (state) { case INVALID_ZEROS: - return INVALID; + return NOT_A_NUMBER; case OWS: case BEGIN: - case ZERO_DIGITS: + case SIGN: state = ON_LEADING_ZEROS; case BEGIN_ZEROS: case FLOAT: ++length; - case BEGIN_FLOAT_DIGITS: + case BEGIN_FRAC_DIGITS: + case BINARY: + case OCTAL: + case DECIMAL: + case HEX: + continue; + case BEGIN_BINARY: + result |= BINARY; + state = BINARY; continue; } case "1": + switch (state) { + case BEGIN_BINARY: + result |= BINARY; + state = BINARY; + case BINARY: + continue; + } case "2": case "3": case "4": case "5": case "6": case "7": + switch (state) { + case BEGIN_OCTAL: + result |= OCTAL; + state = OCTAL; + case OCTAL: + continue; + } case "8": case "9": switch (state) { case FLOAT: length = 0; - case EXPONENT: + case DECIMAL: case HEX: - case INTEGER: + case EXPONENT_INTEGER: continue; - case ZERO_DIGITS: + case SIGN: case BEGIN_ZEROS: case BEGIN: case OWS: - state = INTEGER; + state = DECIMAL; continue; case BEGIN_HEX: result |= HEX; @@ -256,18 +370,17 @@ function analyzeNumber(str, options) { continue; case BEGIN_EXPONENT: case EXPONENT_SIGN: - result |= EXPONENT; - state = EXPONENT; + result |= EXPONENT_INTEGER; + state = EXPONENT_INTEGER; continue; - case BEGIN_FLOAT_DIGITS: + case BEGIN_FRAC_DIGITS: result |= FLOAT; state = FLOAT; continue; default: - return INVALID; + return NOT_A_NUMBER; } case "a": - case "b": case "c": case "d": case "f": @@ -283,56 +396,87 @@ function analyzeNumber(str, options) { case HEX: continue; default: - return INVALID; + return NOT_A_NUMBER; } - case DECIMAL: + case DECIMAL_POINT: switch (state) { case BEGIN: case OWS: - case ZERO_DIGITS: + case SIGN: case INVALID_ZEROS: case BEGIN_ZEROS: - case INTEGER: - state = BEGIN_FLOAT_DIGITS; + case DECIMAL: + state = BEGIN_FRAC_DIGITS; continue; default: - return INVALID; + return NOT_A_NUMBER; } case "e": case "E": switch (state) { + case BEGIN_HEX: + result |= HEX; + state = HEX; + continue; case BEGIN_ZEROS: if (length > 1) { - return INVALID; + return NOT_A_NUMBER; } - case INTEGER: - case BEGIN_FLOAT_DIGITS: + case DECIMAL: + case BEGIN_FRAC_DIGITS: case FLOAT: - result |= EXPONENT; + result |= EXPONENT_INDICATOR; state = ON_E; case HEX: continue; default: - return INVALID; + return NOT_A_NUMBER; + } + case "b": + switch (state) { + case BEGIN_HEX: + result |= HEX; + state = HEX; + case HEX: + continue; + case BEGIN_ZEROS: + if (length !== 1) { + return NOT_A_NUMBER; + } + case INVALID_ZEROS: + state = ON_BINARY; + continue; + default: + return NOT_A_NUMBER; + } + case "n": + switch (state) { + case DECIMAL: + result |= BIGINT + state = BIGINT_LITERAL_SUFFIX + continue; + default: + return NOT_A_NUMBER; } default: - return INVALID; + return NOT_A_NUMBER; } } switch (state) { + case BINARY: + case OCTAL: + case DECIMAL: + case HEX: + case FLOAT: + case BIGINT_LITERAL_SUFFIX: + case EXPONENT_INTEGER: case INVALID_ZEROS: case BEGIN_ZEROS: - case INTEGER: - case BEGIN_FLOAT_DIGITS: - case FLOAT: + case BEGIN_FRAC_DIGITS: case TRAILING_WHITESPACE: - case EXPONENT: - case HEX: return result; default: - return INVALID; + return NOT_A_NUMBER; } } - -console.log(toNumber('1.0e2 ')) \ No newline at end of file From 11bcce66c985a2f5f2c99eacb281418c0479bc66 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Mon, 21 Jul 2025 02:49:43 +0200 Subject: [PATCH 19/36] blub --- strnum.js | 83 ++++++++++++++++++++++++++----------------------------- 1 file changed, 39 insertions(+), 44 deletions(-) diff --git a/strnum.js b/strnum.js index 0eb51a8..897a877 100644 --- a/strnum.js +++ b/strnum.js @@ -135,7 +135,7 @@ const BIGINT_LITERAL_SUFFIX = /** @type {const} */ assertBitmask(2048, 1 << 11); const BEGIN = /** @type {const} */ assertBitmask(2048, 1 << 11); const END = /** @type {const} */ assertBitmask(4096, 1 << 12); -const OWS = /** @type {const} */ assertBitmask(2176, WHITESPACE | BEGIN); +const LEADING_WHITESPACE = /** @type {const} */ assertBitmask(2176, WHITESPACE | BEGIN); const TRAILING_WHITESPACE = /** @type {const} */ assertBitmask(4224, WHITESPACE | END); const BEGIN_INTEGER_DIGITS = /** @type {const} */ assertBitmask(2052, DECIMAL | BEGIN); @@ -143,16 +143,14 @@ const BEGIN_FRAC_DIGITS = /** @type {const} */ assertBitmask(2080, FLOAT | BEGIN const BEGIN_HEX = /** @type {const} */ assertBitmask(2064, HEX | BEGIN); const BEGIN_OCTAL = /** @type {const} */ assertBitmask(2056, OCTAL | BEGIN); const BEGIN_BINARY = /** @type {const} */ assertBitmask(2050, BINARY | BEGIN); -const BEGIN_ZEROS = /** @type {const} */ assertBitmask(2304, BEGIN | ZERO); const BEGIN_EXPONENT = /** @type {const} */ assertBitmask(3072, EXPONENT_INDICATOR | BEGIN); const EXPONENT_SIGN = /** @type {const} */ assertBitmask(1536, EXPONENT_INDICATOR | SIGN); const EXPONENT_INTEGER = /** @type {const} */ assertBitmask(1088, EXPONENT_INDICATOR | INTEGER); -const ZERO_DIGITS = /** @type {const} */ assertBitmask(256, ZERO); - -const INVALID_ZEROS = /** @type {const} */ assertBitmask(257, ZERO | NOT_A_NUMBER); - +const FIRST_DIGIT_ZERO = /** @type {const} */ assertBitmask(2304, ZERO | BEGIN); +const FIRST_DIGIT_ZERO_NOT_LEADING = /** @type {const} */ assertBitmask(6400, ZERO | BEGIN | END); +const LEADING_ZEROS = /** @type {const} */ assertBitmask(2308, ZERO | BEGIN | DECIMAL); /** * @typedef {typeof NUMBER | * typeof NOT_A_NUMBER | @@ -168,7 +166,7 @@ const INVALID_ZEROS = /** @type {const} */ assertBitmask(257, ZERO | NOT_A_NUMBE * typeof WHITESPACE | * typeof BEGIN | * typeof END | - * typeof OWS | + * typeof LEADING_WHITESPACE | * typeof TRAILING_WHITESPACE | * typeof BEGIN_INTEGER_DIGITS | * typeof BEGIN_FRAC_DIGITS | @@ -176,9 +174,9 @@ const INVALID_ZEROS = /** @type {const} */ assertBitmask(257, ZERO | NOT_A_NUMBE * typeof BEGIN_HEX | * typeof BEGIN_OCTAL | * typeof BEGIN_EXPONENT | - * typeof BEGIN_ZEROS | - * typeof ZERO_DIGITS | - * typeof INVALID_ZEROS | + * typeof FIRST_DIGIT_ZERO | + * typeof FIRST_DIGIT_ZERO_NOT_LEADING | + * LEADING_ZEROS | * typeof SIGN | * typeof EXPONENT_INDICATOR | * typeof EXPONENT_SIGN | @@ -219,7 +217,7 @@ export function analyzeNumber(str, options) { const ON_E = options.eNotation !== false ? BEGIN_EXPONENT : NOT_A_NUMBER; const ON_BINARY = BEGIN_BINARY; const ON_OCTAL = BEGIN_OCTAL; - const ON_LEADING_ZEROS = options.leadingZeros === false ? INVALID_ZEROS : BEGIN_ZEROS; + const ON_LEADING_ZEROS = options.leadingZeros === false ? FIRST_DIGIT_ZERO_NOT_LEADING : FIRST_DIGIT_ZERO; while (++pos < len) { switch (str[pos]) { @@ -251,7 +249,7 @@ export function analyzeNumber(str, options) { switch (state) { case BEGIN: result |= WHITESPACE; - state = OWS; + state = LEADING_WHITESPACE; continue; case BINARY: case OCTAL: @@ -262,7 +260,7 @@ export function analyzeNumber(str, options) { case FLOAT: result |= WHITESPACE; state = TRAILING_WHITESPACE; - case OWS: + case LEADING_WHITESPACE: case TRAILING_WHITESPACE: continue; default: @@ -272,7 +270,7 @@ export function analyzeNumber(str, options) { case "-": switch (state) { case BEGIN: - case OWS: + case LEADING_WHITESPACE: result |= SIGN; state = SIGN; continue; @@ -283,24 +281,20 @@ export function analyzeNumber(str, options) { return NOT_A_NUMBER; } case "o": + case "O": switch (state) { - case BEGIN_ZEROS: - if (length !== 1) { - return NOT_A_NUMBER; - } - case INVALID_ZEROS: + case FIRST_DIGIT_ZERO: + case FIRST_DIGIT_ZERO_NOT_LEADING: state = ON_OCTAL; continue; default: return NOT_A_NUMBER; } case "x": + case "X": switch (state) { - case BEGIN_ZEROS: - if (length !== 1) { - return NOT_A_NUMBER; - } - case INVALID_ZEROS: + case FIRST_DIGIT_ZERO: + case FIRST_DIGIT_ZERO_NOT_LEADING: state = ON_HEX; continue; default: @@ -308,13 +302,15 @@ export function analyzeNumber(str, options) { } case "0": switch (state) { - case INVALID_ZEROS: + case FIRST_DIGIT_ZERO_NOT_LEADING: return NOT_A_NUMBER; - case OWS: + case FIRST_DIGIT_ZERO: + state = LEADING_ZEROS; + continue; + case LEADING_WHITESPACE: case BEGIN: case SIGN: state = ON_LEADING_ZEROS; - case BEGIN_ZEROS: case FLOAT: ++length; case BEGIN_FRAC_DIGITS: @@ -359,9 +355,10 @@ export function analyzeNumber(str, options) { case EXPONENT_INTEGER: continue; case SIGN: - case BEGIN_ZEROS: + case FIRST_DIGIT_ZERO: + case LEADING_ZEROS: case BEGIN: - case OWS: + case LEADING_WHITESPACE: state = DECIMAL; continue; case BEGIN_HEX: @@ -385,7 +382,6 @@ export function analyzeNumber(str, options) { case "d": case "f": case "A": - case "B": case "C": case "D": case "F": @@ -401,10 +397,11 @@ export function analyzeNumber(str, options) { case DECIMAL_POINT: switch (state) { case BEGIN: - case OWS: + case LEADING_WHITESPACE: case SIGN: - case INVALID_ZEROS: - case BEGIN_ZEROS: + case FIRST_DIGIT_ZERO: + case FIRST_DIGIT_ZERO_NOT_LEADING: + case LEADING_ZEROS: case DECIMAL: state = BEGIN_FRAC_DIGITS; continue; @@ -418,10 +415,8 @@ export function analyzeNumber(str, options) { result |= HEX; state = HEX; continue; - case BEGIN_ZEROS: - if (length > 1) { - return NOT_A_NUMBER; - } + case FIRST_DIGIT_ZERO: + case FIRST_DIGIT_ZERO_NOT_LEADING: case DECIMAL: case BEGIN_FRAC_DIGITS: case FLOAT: @@ -433,17 +428,15 @@ export function analyzeNumber(str, options) { return NOT_A_NUMBER; } case "b": + case "B": switch (state) { case BEGIN_HEX: result |= HEX; state = HEX; case HEX: continue; - case BEGIN_ZEROS: - if (length !== 1) { - return NOT_A_NUMBER; - } - case INVALID_ZEROS: + case FIRST_DIGIT_ZERO: + case FIRST_DIGIT_ZERO_NOT_LEADING: state = ON_BINARY; continue; default: @@ -469,10 +462,12 @@ export function analyzeNumber(str, options) { case DECIMAL: case HEX: case FLOAT: + case LEADING_ZEROS: case BIGINT_LITERAL_SUFFIX: case EXPONENT_INTEGER: - case INVALID_ZEROS: - case BEGIN_ZEROS: + case FIRST_DIGIT_ZERO: + case FIRST_DIGIT_ZERO_NOT_LEADING: + case LEADING_ZEROS: case BEGIN_FRAC_DIGITS: case TRAILING_WHITESPACE: return result; From 23052336c31d6cca584794b4a933146b4c0f3db8 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Mon, 21 Jul 2025 03:08:18 +0200 Subject: [PATCH 20/36] more --- strnum.d.ts | 50 ++++++++++++++++++++++++++++++++++++++++++++++++-- strnum.js | 30 ++++++++++++++++++++++++------ 2 files changed, 72 insertions(+), 8 deletions(-) diff --git a/strnum.d.ts b/strnum.d.ts index fb585b9..c4202b4 100644 --- a/strnum.d.ts +++ b/strnum.d.ts @@ -6,11 +6,30 @@ declare module "strnum" { * @returns {number|T} - The converted number or the original value if conversion is not applicable. */ export default function toNumber(str: T, options?: Options): number | T; + /** + * @param {string} str - The string to analyze. + * @param {Options} options - Options to control the parsing behavior. + * @returns {number} - A bitmask representing the analysis result of the string. + */ + export function analyzeNumber(str: string, options: Options): number; + export type State = typeof NUMBER | typeof NOT_A_NUMBER | typeof BINARY | typeof DECIMAL | typeof OCTAL | typeof HEX | typeof FLOAT | typeof INTEGER | typeof BIGINT | typeof BIGINT_LITERAL_SUFFIX | typeof ZERO | typeof WHITESPACE | typeof BEGIN | typeof END | typeof LEADING_WHITESPACE | typeof TRAILING_WHITESPACE | typeof BEGIN_INTEGER_DIGITS | typeof BEGIN_FRAC_DIGITS | typeof BEGIN_BINARY | typeof BEGIN_HEX | typeof BEGIN_OCTAL | typeof BEGIN_EXPONENT | typeof FIRST_DIGIT_ZERO | typeof FIRST_DIGIT_ZERO_NOT_LEADING | 2308 | typeof SIGN | typeof EXPONENT_INDICATOR | typeof EXPONENT_SIGN | typeof EXPONENT_INTEGER; export type Options = { /** * - Whether to allow hexadecimal numbers (e.g., "0x1A"). */ hex?: boolean; + /** + * - Whether to allow octal numbers (e.g., "0o17"). + */ + octal?: boolean; + /** + * - Whether to allow binary numbers (e.g., "0b1010"). + */ + binary?: boolean; + /** + * - Whether to allow BigInt numbers (e.g., "123n"). + */ + bigint?: boolean; /** * - Whether to allow leading zeros in numbers. */ @@ -28,6 +47,33 @@ declare module "strnum" { */ eNotation?: boolean; }; - - export function analyzeNumber(str: string, options?: Options): number; + const NUMBER: 0; + const NOT_A_NUMBER: 1; + const BINARY: 2; + const DECIMAL: 4; + const OCTAL: 8; + const HEX: 16; + const FLOAT: 32; + const INTEGER: 64; + const BIGINT: 2112; + const BIGINT_LITERAL_SUFFIX: 2048; + const ZERO: 256; + const WHITESPACE: 128; + const BEGIN: 2048; + const END: 4096; + const LEADING_WHITESPACE: 2176; + const TRAILING_WHITESPACE: 4224; + const BEGIN_INTEGER_DIGITS: 2052; + const BEGIN_FRAC_DIGITS: 2080; + const BEGIN_BINARY: 2050; + const BEGIN_HEX: 2064; + const BEGIN_OCTAL: 2056; + const BEGIN_EXPONENT: 3072; + const FIRST_DIGIT_ZERO: 2304; + const FIRST_DIGIT_ZERO_NOT_LEADING: 6400; + const SIGN: 512; + const EXPONENT_INDICATOR: 1024; + const EXPONENT_SIGN: 1536; + const EXPONENT_INTEGER: 1088; + export {}; } diff --git a/strnum.js b/strnum.js index 897a877..3925254 100644 --- a/strnum.js +++ b/strnum.js @@ -1,6 +1,9 @@ /** * @typedef {Object} Options * @property {boolean} [hex=true] - Whether to allow hexadecimal numbers (e.g., "0x1A"). + * @property {boolean} [octal=false] - Whether to allow octal numbers (e.g., "0o17"). + * @property {boolean} [binary=false] - Whether to allow binary numbers (e.g., "0b1010"). + * @property {boolean} [bigint=false] - Whether to allow BigInt numbers (e.g., "123n"). * @property {boolean} [leadingZeros=true] - Whether to allow leading zeros in numbers. * @property {RegExp} [skipLike] - A regular expression to skip certain string patterns. * @property {string} [decimalPoint="."] - The character used as the decimal point. @@ -24,7 +27,7 @@ const EXP_CHAR = function () { } }(); -/** @type {(string: string, radix: 10|16) => number} */ +/** @type {(string: string, radix: 2|8|10|16) => number} */ const parse_int = ((function parse_int() { if (parseInt) return parseInt; else if (Number.parseInt) return Number.parseInt; @@ -59,7 +62,21 @@ export default function toNumber(str, options = {}) { } if ((analyzeResult & HEX) === HEX) { - return parse_int(str, 16); + return parse_int(str, HEX); + } + + if ((analyzeResult & OCTAL) === OCTAL) { + if ((analyzeResult & WHITESPACE) === WHITESPACE) { + return parse_int(str.trim().slice(2), OCTAL); + } + return parse_int(str.slice(2), OCTAL); + } + + if ((analyzeResult & BINARY) === BINARY) { + if ((analyzeResult & WHITESPACE) === WHITESPACE) { + return parse_int(str.trim().slice(2), BINARY); + } + return parse_int(str.slice(2), BINARY); } if ((analyzeResult & EXPONENT_INDICATOR) === EXPONENT_INDICATOR) { @@ -215,8 +232,9 @@ export function analyzeNumber(str, options) { const DECIMAL_POINT = options.decimalPoint || "\."; const ON_HEX = options.hex !== false ? BEGIN_HEX : NOT_A_NUMBER; const ON_E = options.eNotation !== false ? BEGIN_EXPONENT : NOT_A_NUMBER; - const ON_BINARY = BEGIN_BINARY; - const ON_OCTAL = BEGIN_OCTAL; + const ON_BIGINT = options.bigint === true ? BIGINT_LITERAL_SUFFIX : NOT_A_NUMBER; + const ON_BINARY = options.binary === true ? BEGIN_BINARY : NOT_A_NUMBER; + const ON_OCTAL = options.octal === true ? BEGIN_OCTAL : NOT_A_NUMBER; const ON_LEADING_ZEROS = options.leadingZeros === false ? FIRST_DIGIT_ZERO_NOT_LEADING : FIRST_DIGIT_ZERO; while (++pos < len) { @@ -445,8 +463,8 @@ export function analyzeNumber(str, options) { case "n": switch (state) { case DECIMAL: - result |= BIGINT - state = BIGINT_LITERAL_SUFFIX + result |= BIGINT; + state = ON_BIGINT; continue; default: return NOT_A_NUMBER; From a69eb2e2ac3c112b2cae7e05666d142bc46aba80 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Mon, 21 Jul 2025 03:24:04 +0200 Subject: [PATCH 21/36] fix --- strnum.js | 194 +++++++++++++++++++++++++++--------------------------- 1 file changed, 97 insertions(+), 97 deletions(-) diff --git a/strnum.js b/strnum.js index 3925254..c5aa1b1 100644 --- a/strnum.js +++ b/strnum.js @@ -17,7 +17,7 @@ * @constant */ const EXP_CHAR = function () { - const bigNumberAsString = String(1e100) + const bigNumberAsString = '' + 1e100; if (bigNumberAsString.indexOf("e") !== -1) { return "e"; } else if (bigNumberAsString.indexOf("E") !== -1) { @@ -81,13 +81,13 @@ export default function toNumber(str, options = {}) { if ((analyzeResult & EXPONENT_INDICATOR) === EXPONENT_INDICATOR) { if (options.eNotation !== false) { - return Number(str); + return +str; } return str; } - const num = (analyzeResult & INTEGER) === INTEGER ? parse_int(str, 10) : Number(str); - const parsedStr = String(num); + const num = (analyzeResult & INTEGER) === INTEGER ? parse_int(str, 10) : +str; + const parsedStr = '' + num; if (parsedStr.indexOf(EXP_CHAR) !== -1) { if (options.eNotation !== false) return num; @@ -239,85 +239,6 @@ export function analyzeNumber(str, options) { while (++pos < len) { switch (str[pos]) { - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#white_space - case " ": - case "\t": - case "\v": - case "\f": - case "\r": - case "\n": - case "\ufeff": // Unicode line separator - // https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5Cp%7BGeneral_Category%3DSpace_Separator%7D - case "\u00A0": // Non-breaking space - case "\u1680": // Ogham space mark - case "\u2000": // En quad - case "\u2001": // Em quad - case "\u2002": // En space - case "\u2003": // Em space - case "\u2004": // Three-per-em space - case "\u2005": // Four-per-em space - case "\u2006": // Six-per-em space - case "\u2007": // Figure space - case "\u2008": // Punctuation space - case "\u2009": // Thin space - case "\u200A": // Hair space - case "\u202F": // Narrow no-break space - case "\u205F": // Medium mathematical space - case "\u3000": // Ideographic space - switch (state) { - case BEGIN: - result |= WHITESPACE; - state = LEADING_WHITESPACE; - continue; - case BINARY: - case OCTAL: - case DECIMAL: - case HEX: - case EXPONENT_INTEGER: - case BIGINT_LITERAL_SUFFIX: - case FLOAT: - result |= WHITESPACE; - state = TRAILING_WHITESPACE; - case LEADING_WHITESPACE: - case TRAILING_WHITESPACE: - continue; - default: - return NOT_A_NUMBER; - } - case "+": - case "-": - switch (state) { - case BEGIN: - case LEADING_WHITESPACE: - result |= SIGN; - state = SIGN; - continue; - case BEGIN_EXPONENT: - state = EXPONENT_SIGN; - continue; - default: - return NOT_A_NUMBER; - } - case "o": - case "O": - switch (state) { - case FIRST_DIGIT_ZERO: - case FIRST_DIGIT_ZERO_NOT_LEADING: - state = ON_OCTAL; - continue; - default: - return NOT_A_NUMBER; - } - case "x": - case "X": - switch (state) { - case FIRST_DIGIT_ZERO: - case FIRST_DIGIT_ZERO_NOT_LEADING: - state = ON_HEX; - continue; - default: - return NOT_A_NUMBER; - } case "0": switch (state) { case FIRST_DIGIT_ZERO_NOT_LEADING: @@ -412,16 +333,17 @@ export function analyzeNumber(str, options) { default: return NOT_A_NUMBER; } - case DECIMAL_POINT: + case "b": + case "B": switch (state) { - case BEGIN: - case LEADING_WHITESPACE: - case SIGN: + case BEGIN_HEX: + result |= HEX; + state = HEX; + case HEX: + continue; case FIRST_DIGIT_ZERO: case FIRST_DIGIT_ZERO_NOT_LEADING: - case LEADING_ZEROS: - case DECIMAL: - state = BEGIN_FRAC_DIGITS; + state = ON_BINARY; continue; default: return NOT_A_NUMBER; @@ -445,17 +367,50 @@ export function analyzeNumber(str, options) { default: return NOT_A_NUMBER; } - case "b": - case "B": + case "+": + case "-": switch (state) { - case BEGIN_HEX: - result |= HEX; - state = HEX; - case HEX: + case BEGIN: + case LEADING_WHITESPACE: + result |= SIGN; + state = SIGN; + continue; + case BEGIN_EXPONENT: + state = EXPONENT_SIGN; continue; + default: + return NOT_A_NUMBER; + } + case DECIMAL_POINT: + switch (state) { + case BEGIN: + case LEADING_WHITESPACE: + case SIGN: case FIRST_DIGIT_ZERO: case FIRST_DIGIT_ZERO_NOT_LEADING: - state = ON_BINARY; + case LEADING_ZEROS: + case DECIMAL: + state = BEGIN_FRAC_DIGITS; + continue; + default: + return NOT_A_NUMBER; + } + case "x": + case "X": + switch (state) { + case FIRST_DIGIT_ZERO: + case FIRST_DIGIT_ZERO_NOT_LEADING: + state = ON_HEX; + continue; + default: + return NOT_A_NUMBER; + } + case "o": + case "O": + switch (state) { + case FIRST_DIGIT_ZERO: + case FIRST_DIGIT_ZERO_NOT_LEADING: + state = ON_OCTAL; continue; default: return NOT_A_NUMBER; @@ -469,6 +424,51 @@ export function analyzeNumber(str, options) { default: return NOT_A_NUMBER; } + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#white_space + case " ": + case "\t": + case "\v": + case "\f": + case "\r": + case "\n": + case "\ufeff": // Unicode line separator + // https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5Cp%7BGeneral_Category%3DSpace_Separator%7D + case "\u00A0": // Non-breaking space + case "\u1680": // Ogham space mark + case "\u2000": // En quad + case "\u2001": // Em quad + case "\u2002": // En space + case "\u2003": // Em space + case "\u2004": // Three-per-em space + case "\u2005": // Four-per-em space + case "\u2006": // Six-per-em space + case "\u2007": // Figure space + case "\u2008": // Punctuation space + case "\u2009": // Thin space + case "\u200A": // Hair space + case "\u202F": // Narrow no-break space + case "\u205F": // Medium mathematical space + case "\u3000": // Ideographic space + switch (state) { + case BEGIN: + result |= WHITESPACE; + state = LEADING_WHITESPACE; + continue; + case BINARY: + case OCTAL: + case DECIMAL: + case HEX: + case EXPONENT_INTEGER: + case BIGINT_LITERAL_SUFFIX: + case FLOAT: + result |= WHITESPACE; + state = TRAILING_WHITESPACE; + case LEADING_WHITESPACE: + case TRAILING_WHITESPACE: + continue; + default: + return NOT_A_NUMBER; + } default: return NOT_A_NUMBER; } From a531870cfb5af5b3f9b4c74e6e080b5abe53f362 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Mon, 21 Jul 2025 03:48:07 +0200 Subject: [PATCH 22/36] add infinity --- analyze-number.test.js | 6 ------ benchmark.js | 15 ++++++++++++++- strnum.js | 29 +++++++++++++++++++++++++++-- strnum.test.js | 14 ++++++++++++++ 4 files changed, 55 insertions(+), 9 deletions(-) delete mode 100644 analyze-number.test.js diff --git a/analyze-number.test.js b/analyze-number.test.js deleted file mode 100644 index f083d24..0000000 --- a/analyze-number.test.js +++ /dev/null @@ -1,6 +0,0 @@ -import { analyzeNumber } from "./strnum.js"; - -describe("analyzeNumber", () => { - it("", () => { - }); -}) \ No newline at end of file diff --git a/benchmark.js b/benchmark.js index 6dc4da3..078673f 100644 --- a/benchmark.js +++ b/benchmark.js @@ -4,7 +4,7 @@ import toNumber from "./strnum.js" const bench = new Bench({ name: 'strnum benchmark', time: 100 }) function toNumberBenchmark(str, options) { - bench.add(`${str}${options ? JSON.stringify(options) :''}`, () => { + bench.add(`${str}${options ? ', ' + JSON.stringify(options) : ''}`, () => { toNumber(str, options) }) } @@ -141,6 +141,19 @@ toNumberBenchmark("+12.12"); toNumberBenchmark("-12.12"); toNumberBenchmark("-012.12"); +toNumberBenchmark("Infinity"); +toNumberBenchmark("-Infinity"); +toNumberBenchmark("+Infinity"); +toNumberBenchmark("Infinity", { infinity: true }); +toNumberBenchmark("-Infinity", { infinity: true }); +toNumberBenchmark("+Infinity", { infinity: true }); +toNumberBenchmark("Infinity", { infinity: false }); +toNumberBenchmark("-Infinity", { infinity: false }); +toNumberBenchmark("+Infinity", { infinity: false }); +toNumberBenchmark(" Infinity "); +toNumberBenchmark(" -Infinity "); +toNumberBenchmark(" +Infinity "); + await bench.run() console.log(bench.name) diff --git a/strnum.js b/strnum.js index c5aa1b1..81d7a2b 100644 --- a/strnum.js +++ b/strnum.js @@ -5,6 +5,7 @@ * @property {boolean} [binary=false] - Whether to allow binary numbers (e.g., "0b1010"). * @property {boolean} [bigint=false] - Whether to allow BigInt numbers (e.g., "123n"). * @property {boolean} [leadingZeros=true] - Whether to allow leading zeros in numbers. + * @property {boolean} [infinity=false] - Whether to allow "Infinity" and "-Infinity". * @property {RegExp} [skipLike] - A regular expression to skip certain string patterns. * @property {string} [decimalPoint="."] - The character used as the decimal point. * @property {boolean} [eNotation=true] - Whether to allow scientific notation (e.g., "1e10"). @@ -86,6 +87,10 @@ export default function toNumber(str, options = {}) { return str; } + if ((analyzeResult & INFINITY) === INFINITY) { + return +str; + } + const num = (analyzeResult & INTEGER) === INTEGER ? parse_int(str, 10) : +str; const parsedStr = '' + num; @@ -147,7 +152,6 @@ const SIGN = /** @type {const} */ assertBitmask(512, 1 << 9); const EXPONENT_INDICATOR = /** @type {const} */ assertBitmask(1024, 1 << 10); // 'e' or 'E' const BIGINT_LITERAL_SUFFIX = /** @type {const} */ assertBitmask(2048, 1 << 11); // 'n' for BigInt - // Positional constants const BEGIN = /** @type {const} */ assertBitmask(2048, 1 << 11); const END = /** @type {const} */ assertBitmask(4096, 1 << 12); @@ -168,6 +172,9 @@ const EXPONENT_INTEGER = /** @type {const} */ assertBitmask(1088, EXPONENT_INDIC const FIRST_DIGIT_ZERO = /** @type {const} */ assertBitmask(2304, ZERO | BEGIN); const FIRST_DIGIT_ZERO_NOT_LEADING = /** @type {const} */ assertBitmask(6400, ZERO | BEGIN | END); const LEADING_ZEROS = /** @type {const} */ assertBitmask(2308, ZERO | BEGIN | DECIMAL); + +const INFINITY = /** @type {const} */ assertBitmask(8192, 1 << 13); + /** * @typedef {typeof NUMBER | * typeof NOT_A_NUMBER | @@ -193,7 +200,8 @@ const LEADING_ZEROS = /** @type {const} */ assertBitmask(2308, ZERO | BEGIN | DE * typeof BEGIN_EXPONENT | * typeof FIRST_DIGIT_ZERO | * typeof FIRST_DIGIT_ZERO_NOT_LEADING | - * LEADING_ZEROS | + * typeof LEADING_ZEROS | + * typeof INFINITY | * typeof SIGN | * typeof EXPONENT_INDICATOR | * typeof EXPONENT_SIGN | @@ -236,6 +244,7 @@ export function analyzeNumber(str, options) { const ON_BINARY = options.binary === true ? BEGIN_BINARY : NOT_A_NUMBER; const ON_OCTAL = options.octal === true ? BEGIN_OCTAL : NOT_A_NUMBER; const ON_LEADING_ZEROS = options.leadingZeros === false ? FIRST_DIGIT_ZERO_NOT_LEADING : FIRST_DIGIT_ZERO; + const ON_INFINITY = options.infinity === true ? INFINITY : NOT_A_NUMBER; while (++pos < len) { switch (str[pos]) { @@ -424,6 +433,20 @@ export function analyzeNumber(str, options) { default: return NOT_A_NUMBER; } + case "I": + if ( + str[++pos] === "n" && + str[++pos] === "f" && + str[++pos] === "i" && + str[++pos] === "n" && + str[++pos] === "i" && + str[++pos] === "t" && + str[++pos] === "y" + ) { + result |= INFINITY; + state = ON_INFINITY; + } + break; // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#white_space case " ": case "\t": @@ -461,6 +484,7 @@ export function analyzeNumber(str, options) { case EXPONENT_INTEGER: case BIGINT_LITERAL_SUFFIX: case FLOAT: + case INFINITY: result |= WHITESPACE; state = TRAILING_WHITESPACE; case LEADING_WHITESPACE: @@ -488,6 +512,7 @@ export function analyzeNumber(str, options) { case LEADING_ZEROS: case BEGIN_FRAC_DIGITS: case TRAILING_WHITESPACE: + case INFINITY: return result; default: return NOT_A_NUMBER; diff --git a/strnum.test.js b/strnum.test.js index cc38469..4ff629e 100644 --- a/strnum.test.js +++ b/strnum.test.js @@ -183,4 +183,18 @@ describe("Should convert all the valid numeric strings to number", () => { expect(toNumber("-12.12")).toEqual(-12.12); expect(toNumber("-012.12")).toEqual(-12.12); }) + it("Infinity", () => { + expect(toNumber("Infinity")).toEqual("Infinity"); + expect(toNumber("-Infinity")).toEqual("-Infinity"); + expect(toNumber("+Infinity")).toEqual("+Infinity"); + expect(toNumber("Infinity", { infinity: true })).toEqual(Infinity); + expect(toNumber("-Infinity", { infinity: true })).toEqual(-Infinity); + expect(toNumber("+Infinity", { infinity: true })).toEqual(+Infinity); + expect(toNumber("Infinity", { infinity: false })).toEqual("Infinity"); + expect(toNumber("-Infinity", { infinity: false })).toEqual("-Infinity"); + expect(toNumber("+Infinity", { infinity: false })).toEqual("+Infinity"); + expect(toNumber(" Infinity ")).toEqual(" Infinity "); + expect(toNumber(" -Infinity ")).toEqual(" -Infinity "); + expect(toNumber(" +Infinity ")).toEqual(" +Infinity "); + }) }); From e54c9eefd7550da7491c22025bf44600306bb0db Mon Sep 17 00:00:00 2001 From: Amit K Gupta Date: Mon, 21 Jul 2025 08:24:17 +0530 Subject: [PATCH 23/36] Delete package-lock.json --- package-lock.json | 582 ---------------------------------------------- 1 file changed, 582 deletions(-) delete mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index b4920e9..0000000 --- a/package-lock.json +++ /dev/null @@ -1,582 +0,0 @@ -{ - "name": "strnum", - "version": "2.1.1", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "strnum", - "version": "2.1.1", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "devDependencies": { - "jasmine": "^5.6.0", - "tinybench": "^4.0.1", - "typescript": "^5.8.3" - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/jasmine": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-5.8.0.tgz", - "integrity": "sha512-1V6HGa0+TMoMY20+/vp++RqLlL1noupV8awzV6CiPuICC0g7iKZ9z87zV2KyelRyoig0G1lHn7ueElXVMGVagg==", - "dev": true, - "license": "MIT", - "dependencies": { - "glob": "^10.2.2", - "jasmine-core": "~5.8.0" - }, - "bin": { - "jasmine": "bin/jasmine.js" - } - }, - "node_modules/jasmine-core": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.8.0.tgz", - "integrity": "sha512-Q9dqmpUAfptwyueW3+HqBOkSuYd9I/clZSSfN97wXE/Nr2ROFNCwIBEC1F6kb3QXS9Fcz0LjFYSDQT+BiwjuhA==", - "dev": true, - "license": "MIT" - }, - "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/tinybench": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-4.0.1.tgz", - "integrity": "sha512-Nb1srn7dvzkVx0J5h1vq8f48e3TIcbrS7e/UfAI/cDSef/n8yLh4zsAEsFkfpw6auTY+ZaspEvam/xs8nMnotQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - } - } -} From 6a02a99e0a3f200ca5caf869d08c5cd20c057d52 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Tue, 22 Jul 2025 03:57:55 +0200 Subject: [PATCH 24/36] fix Infinity --- strnum.d.ts | 8 +++++++- strnum.js | 31 +++++++++++++++++++------------ strnum.test.js | 3 +++ 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/strnum.d.ts b/strnum.d.ts index c4202b4..f3d1630 100644 --- a/strnum.d.ts +++ b/strnum.d.ts @@ -12,7 +12,7 @@ declare module "strnum" { * @returns {number} - A bitmask representing the analysis result of the string. */ export function analyzeNumber(str: string, options: Options): number; - export type State = typeof NUMBER | typeof NOT_A_NUMBER | typeof BINARY | typeof DECIMAL | typeof OCTAL | typeof HEX | typeof FLOAT | typeof INTEGER | typeof BIGINT | typeof BIGINT_LITERAL_SUFFIX | typeof ZERO | typeof WHITESPACE | typeof BEGIN | typeof END | typeof LEADING_WHITESPACE | typeof TRAILING_WHITESPACE | typeof BEGIN_INTEGER_DIGITS | typeof BEGIN_FRAC_DIGITS | typeof BEGIN_BINARY | typeof BEGIN_HEX | typeof BEGIN_OCTAL | typeof BEGIN_EXPONENT | typeof FIRST_DIGIT_ZERO | typeof FIRST_DIGIT_ZERO_NOT_LEADING | 2308 | typeof SIGN | typeof EXPONENT_INDICATOR | typeof EXPONENT_SIGN | typeof EXPONENT_INTEGER; + export type State = typeof NUMBER | typeof NOT_A_NUMBER | typeof BINARY | typeof DECIMAL | typeof OCTAL | typeof HEX | typeof FLOAT | typeof INTEGER | typeof BIGINT | typeof BIGINT_LITERAL_SUFFIX | typeof ZERO | typeof WHITESPACE | typeof BEGIN | typeof END | typeof LEADING_WHITESPACE | typeof TRAILING_WHITESPACE | typeof BEGIN_INTEGER_DIGITS | typeof BEGIN_FRAC_DIGITS | typeof BEGIN_BINARY | typeof BEGIN_HEX | typeof BEGIN_OCTAL | typeof BEGIN_EXPONENT | typeof FIRST_DIGIT_ZERO | typeof FIRST_DIGIT_ZERO_NOT_LEADING | typeof LEADING_ZEROS | typeof INFINITY | typeof SIGN | typeof EXPONENT_INDICATOR | typeof EXPONENT_SIGN | typeof EXPONENT_INTEGER; export type Options = { /** * - Whether to allow hexadecimal numbers (e.g., "0x1A"). @@ -34,6 +34,10 @@ declare module "strnum" { * - Whether to allow leading zeros in numbers. */ leadingZeros?: boolean; + /** + * - Whether to allow "Infinity" and "-Infinity". + */ + infinity?: boolean; /** * - A regular expression to skip certain string patterns. */ @@ -71,6 +75,8 @@ declare module "strnum" { const BEGIN_EXPONENT: 3072; const FIRST_DIGIT_ZERO: 2304; const FIRST_DIGIT_ZERO_NOT_LEADING: 6400; + const LEADING_ZEROS: 2308; + const INFINITY: 8192; const SIGN: 512; const EXPONENT_INDICATOR: 1024; const EXPONENT_SIGN: 1536; diff --git a/strnum.js b/strnum.js index 81d7a2b..8cabf01 100644 --- a/strnum.js +++ b/strnum.js @@ -434,19 +434,26 @@ export function analyzeNumber(str, options) { return NOT_A_NUMBER; } case "I": - if ( - str[++pos] === "n" && - str[++pos] === "f" && - str[++pos] === "i" && - str[++pos] === "n" && - str[++pos] === "i" && - str[++pos] === "t" && - str[++pos] === "y" - ) { - result |= INFINITY; - state = ON_INFINITY; + switch (state) { + case BEGIN: + case LEADING_WHITESPACE: + case SIGN: + if ( + str[++pos] === "n" && + str[++pos] === "f" && + str[++pos] === "i" && + str[++pos] === "n" && + str[++pos] === "i" && + str[++pos] === "t" && + str[++pos] === "y" + ) { + result |= INFINITY; + state = ON_INFINITY; + continue; + } + default: + return NOT_A_NUMBER; } - break; // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#white_space case " ": case "\t": diff --git a/strnum.test.js b/strnum.test.js index 4ff629e..0cca763 100644 --- a/strnum.test.js +++ b/strnum.test.js @@ -196,5 +196,8 @@ describe("Should convert all the valid numeric strings to number", () => { expect(toNumber(" Infinity ")).toEqual(" Infinity "); expect(toNumber(" -Infinity ")).toEqual(" -Infinity "); expect(toNumber(" +Infinity ")).toEqual(" +Infinity "); + expect(toNumber(" Infinity ", { infinity: true })).toEqual(Infinity); + expect(toNumber(" -Infinity ", { infinity: true })).toEqual(-Infinity); + expect(toNumber(" +Infinity ", { infinity: true })).toEqual(Infinity); }) }); From d69fd64c3756d39d9f00891e2a62622ae2792fbd Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Tue, 22 Jul 2025 06:18:19 +0200 Subject: [PATCH 25/36] fix signed prefixed numbers --- strnum.js | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/strnum.js b/strnum.js index 8cabf01..c950a93 100644 --- a/strnum.js +++ b/strnum.js @@ -68,16 +68,30 @@ export default function toNumber(str, options = {}) { if ((analyzeResult & OCTAL) === OCTAL) { if ((analyzeResult & WHITESPACE) === WHITESPACE) { + const trimmedStr = str.trim(); + if (analyzeResult & SIGN) { + return parse_int((analyzeResult & NEGATIVE ? '-' : '+') + trimmedStr.slice(3), OCTAL); + } return parse_int(str.trim().slice(2), OCTAL); } - return parse_int(str.slice(2), OCTAL); + if (analyzeResult & SIGN) { + return parse_int((analyzeResult & NEGATIVE ? '-' : '+') + str.slice(3), OCTAL); + } + return parse_int(str.trim().slice(2), OCTAL); } if ((analyzeResult & BINARY) === BINARY) { if ((analyzeResult & WHITESPACE) === WHITESPACE) { + const trimmedStr = str.trim(); + if (analyzeResult & SIGN) { + return parse_int((analyzeResult & NEGATIVE ? '-' : '+') + trimmedStr.slice(3), BINARY); + } return parse_int(str.trim().slice(2), BINARY); } - return parse_int(str.slice(2), BINARY); + if (analyzeResult & SIGN) { + return parse_int((analyzeResult & NEGATIVE ? '-' : '+') + str.slice(3), BINARY); + } + return parse_int(str.trim().slice(2), BINARY); } if ((analyzeResult & EXPONENT_INDICATOR) === EXPONENT_INDICATOR) { @@ -88,7 +102,7 @@ export default function toNumber(str, options = {}) { } if ((analyzeResult & INFINITY) === INFINITY) { - return +str; + return analyzeResult & NEGATIVE ? -Infinity : Infinity; } const num = (analyzeResult & INTEGER) === INTEGER ? parse_int(str, 10) : +str; @@ -174,6 +188,7 @@ const FIRST_DIGIT_ZERO_NOT_LEADING = /** @type {const} */ assertBitmask(6400, ZE const LEADING_ZEROS = /** @type {const} */ assertBitmask(2308, ZERO | BEGIN | DECIMAL); const INFINITY = /** @type {const} */ assertBitmask(8192, 1 << 13); +const NEGATIVE = /** @type {const} */ assertBitmask(16384, 1 << 14); /** * @typedef {typeof NUMBER | @@ -259,6 +274,7 @@ export function analyzeNumber(str, options) { case BEGIN: case SIGN: state = ON_LEADING_ZEROS; + continue; case FLOAT: ++length; case BEGIN_FRAC_DIGITS: @@ -376,8 +392,16 @@ export function analyzeNumber(str, options) { default: return NOT_A_NUMBER; } - case "+": case "-": + switch (state) { + case BEGIN: + case LEADING_WHITESPACE: + result |= SIGN; + result |= NEGATIVE; + state = SIGN; + continue; + } + case "+": switch (state) { case BEGIN: case LEADING_WHITESPACE: From 167ba250c9f2f0620760a6e4bdd2d3880b1a411e Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Tue, 22 Jul 2025 06:28:31 +0200 Subject: [PATCH 26/36] add missing whitespace --- strnum.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/strnum.js b/strnum.js index c950a93..4af89e5 100644 --- a/strnum.js +++ b/strnum.js @@ -500,6 +500,8 @@ export function analyzeNumber(str, options) { case "\u2008": // Punctuation space case "\u2009": // Thin space case "\u200A": // Hair space + case "\u2028": // Line separator + case "\u2029": // Paragraph separator case "\u202F": // Narrow no-break space case "\u205F": // Medium mathematical space case "\u3000": // Ideographic space From 26ed5518178ef5e55de1494e04cf9d4c0074517d Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Thu, 24 Jul 2025 19:31:55 +0200 Subject: [PATCH 27/36] better --- package.json | 2 +- strnum.d.ts | 70 +++++++++++------- strnum.js | 200 +++++++++++++++++++++++++++++++-------------------- 3 files changed, 167 insertions(+), 105 deletions(-) diff --git a/package.json b/package.json index 7c7c322..a65bf4c 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "main": "strnum.js", "types": "strnum.d.ts", "scripts": { - "test": "jasmine strnum.test.js", + "test": "jasmine *.test.js", "types": "tsc -p ." }, "keywords": [ diff --git a/strnum.d.ts b/strnum.d.ts index f3d1630..ca235b8 100644 --- a/strnum.d.ts +++ b/strnum.d.ts @@ -12,7 +12,7 @@ declare module "strnum" { * @returns {number} - A bitmask representing the analysis result of the string. */ export function analyzeNumber(str: string, options: Options): number; - export type State = typeof NUMBER | typeof NOT_A_NUMBER | typeof BINARY | typeof DECIMAL | typeof OCTAL | typeof HEX | typeof FLOAT | typeof INTEGER | typeof BIGINT | typeof BIGINT_LITERAL_SUFFIX | typeof ZERO | typeof WHITESPACE | typeof BEGIN | typeof END | typeof LEADING_WHITESPACE | typeof TRAILING_WHITESPACE | typeof BEGIN_INTEGER_DIGITS | typeof BEGIN_FRAC_DIGITS | typeof BEGIN_BINARY | typeof BEGIN_HEX | typeof BEGIN_OCTAL | typeof BEGIN_EXPONENT | typeof FIRST_DIGIT_ZERO | typeof FIRST_DIGIT_ZERO_NOT_LEADING | typeof LEADING_ZEROS | typeof INFINITY | typeof SIGN | typeof EXPONENT_INDICATOR | typeof EXPONENT_SIGN | typeof EXPONENT_INTEGER; + export type State = typeof NUMBER | typeof NOT_A_NUMBER | typeof BINARY | typeof DECIMAL | typeof OCTAL | typeof HEX | typeof FLOAT | typeof INTEGER | typeof BIGINT | typeof BIGINT_LITERAL_SUFFIX | typeof ZERO | typeof WHITESPACE | typeof BEGIN | typeof END | typeof LEADING_WHITESPACE | typeof TRAILING_WHITESPACE | typeof BEGIN_INTEGER_DIGITS | typeof BEGIN_FRAC_DIGITS | typeof BEGIN_BINARY | typeof BEGIN_HEX | typeof BEGIN_OCTAL | typeof BEGIN_EXPONENT | typeof BEGIN_ZERO | typeof FIRST_DIGIT_ZERO_NOT_LEADING | typeof LEADING_ZEROS | typeof INFINITY | typeof SIGN | typeof EXPONENT_INDICATOR | typeof EXPONENT_SIGN | typeof EXPONENT_INTEGER; export type Options = { /** * - Whether to allow hexadecimal numbers (e.g., "0x1A"). @@ -42,14 +42,30 @@ declare module "strnum" { * - A regular expression to skip certain string patterns. */ skipLike?: RegExp; - /** - * - The character used as the decimal point. - */ - decimalPoint?: string; /** * - Whether to allow scientific notation (e.g., "1e10"). */ eNotation?: boolean; + /** + * - Whether to treat empty strings or strings with whitespace as zero. + */ + empty?: boolean; + /** + * - Whether to return NaN for non-numeric values. + */ + NaN?: boolean; + /** + * - Whether to convert boolean values to numbers (true to 1, false to 0). + */ + boolean?: boolean; + /** + * - Whether to convert null to 0. + */ + null?: boolean; + /** + * - Whether to force IEEE 754 compliance for floating-point numbers. + */ + ieee754?: boolean; }; const NUMBER: 0; const NOT_A_NUMBER: 1; @@ -59,27 +75,27 @@ declare module "strnum" { const HEX: 16; const FLOAT: 32; const INTEGER: 64; - const BIGINT: 2112; - const BIGINT_LITERAL_SUFFIX: 2048; - const ZERO: 256; - const WHITESPACE: 128; - const BEGIN: 2048; - const END: 4096; - const LEADING_WHITESPACE: 2176; - const TRAILING_WHITESPACE: 4224; - const BEGIN_INTEGER_DIGITS: 2052; - const BEGIN_FRAC_DIGITS: 2080; - const BEGIN_BINARY: 2050; - const BEGIN_HEX: 2064; - const BEGIN_OCTAL: 2056; - const BEGIN_EXPONENT: 3072; - const FIRST_DIGIT_ZERO: 2304; - const FIRST_DIGIT_ZERO_NOT_LEADING: 6400; - const LEADING_ZEROS: 2308; - const INFINITY: 8192; - const SIGN: 512; - const EXPONENT_INDICATOR: 1024; - const EXPONENT_SIGN: 1536; - const EXPONENT_INTEGER: 1088; + const BIGINT: 128; + const BIGINT_LITERAL_SUFFIX: 8192; + const ZERO: 2048; + const WHITESPACE: 512; + const BEGIN: 16384; + const END: 32768; + const LEADING_WHITESPACE: 16896; + const TRAILING_WHITESPACE: 33280; + const BEGIN_INTEGER_DIGITS: 16388; + const BEGIN_FRAC_DIGITS: 16416; + const BEGIN_BINARY: 16386; + const BEGIN_HEX: 16400; + const BEGIN_OCTAL: 16392; + const BEGIN_EXPONENT: 20480; + const BEGIN_ZERO: 18432; + const FIRST_DIGIT_ZERO_NOT_LEADING: 51200; + const LEADING_ZEROS: 18436; + const INFINITY: 256; + const SIGN: 1024; + const EXPONENT_INDICATOR: 4096; + const EXPONENT_SIGN: 5120; + const EXPONENT_INTEGER: 4160; export {}; } diff --git a/strnum.js b/strnum.js index 4af89e5..3204cf3 100644 --- a/strnum.js +++ b/strnum.js @@ -4,11 +4,15 @@ * @property {boolean} [octal=false] - Whether to allow octal numbers (e.g., "0o17"). * @property {boolean} [binary=false] - Whether to allow binary numbers (e.g., "0b1010"). * @property {boolean} [bigint=false] - Whether to allow BigInt numbers (e.g., "123n"). - * @property {boolean} [leadingZeros=true] - Whether to allow leading zeros in numbers. + * @property {boolean} [leadingZeros=true] - Whether to allow leading zeros in numbers (e.g., "000123"). * @property {boolean} [infinity=false] - Whether to allow "Infinity" and "-Infinity". * @property {RegExp} [skipLike] - A regular expression to skip certain string patterns. - * @property {string} [decimalPoint="."] - The character used as the decimal point. * @property {boolean} [eNotation=true] - Whether to allow scientific notation (e.g., "1e10"). + * @property {boolean} [empty=false] - Whether to treat empty strings or strings with whitespace as zero (e.g., " "). + * @property {boolean} [NaN=false] - Whether to return NaN for non-numeric values, (e.g., "abc" => NaN). + * @property {boolean} [boolean=false] - Whether to convert boolean values to numbers (true to 1, false to 0). + * @property {boolean} [null=false] - Whether to convert null to 0. + * @property {boolean} [ieee754=false] - Whether to force IEEE 754 compliance for floating-point numbers (e.g. "1234567890.1234567890" => 1234567890.1234567). */ /** @@ -45,16 +49,31 @@ const parse_int = ((function parse_int() { * @returns {number|T} - The converted number or the original value if conversion is not applicable. */ export default function toNumber(str, options = {}) { - if (!str || typeof str !== "string") return str; + if (typeof str !== "string") { + if (options.boolean === true) { + if (str === true) return 1; + else if (str === false) return 0; + } + if (options.null === true) { + if (str === null) return 0; + } + return str; + } const analyzeResult = analyzeNumber(str, options); - if (analyzeResult === NOT_A_NUMBER) { + if ((analyzeResult & NOT_A_NUMBER) === NOT_A_NUMBER) { + + if (options.NaN === true) { + return NaN; + } return str; } + let trimmedStr; + if (options.skipLike !== undefined) { - const trimmedStr = ((analyzeResult & WHITESPACE) === WHITESPACE) + trimmedStr = ((analyzeResult & WHITESPACE) === WHITESPACE) ? str.trim() : str; if (options.skipLike.test(trimmedStr)) { @@ -62,36 +81,29 @@ export default function toNumber(str, options = {}) { } } - if ((analyzeResult & HEX) === HEX) { - return parse_int(str, HEX); + if ((analyzeResult & ZERO) === ZERO) { + return 0; } - if ((analyzeResult & OCTAL) === OCTAL) { - if ((analyzeResult & WHITESPACE) === WHITESPACE) { - const trimmedStr = str.trim(); - if (analyzeResult & SIGN) { - return parse_int((analyzeResult & NEGATIVE ? '-' : '+') + trimmedStr.slice(3), OCTAL); - } - return parse_int(str.trim().slice(2), OCTAL); - } - if (analyzeResult & SIGN) { - return parse_int((analyzeResult & NEGATIVE ? '-' : '+') + str.slice(3), OCTAL); - } - return parse_int(str.trim().slice(2), OCTAL); + if ((analyzeResult & HEX) === HEX) { + return parse_int(str, HEX); } - if ((analyzeResult & BINARY) === BINARY) { + if ((analyzeResult & REMOVE_TYPE_HINT) !== 0) { + const BASE = /** @type {2|8} */ (analyzeResult & OCTAL || analyzeResult & BINARY); + trimmedStr = trimmedStr || ((analyzeResult & WHITESPACE) === WHITESPACE) + ? str.trim() + : str; if ((analyzeResult & WHITESPACE) === WHITESPACE) { - const trimmedStr = str.trim(); if (analyzeResult & SIGN) { - return parse_int((analyzeResult & NEGATIVE ? '-' : '+') + trimmedStr.slice(3), BINARY); + return parse_int((analyzeResult & NEGATIVE ? '-' : '+') + trimmedStr.slice(3), BASE); } - return parse_int(str.trim().slice(2), BINARY); + return parse_int(trimmedStr.slice(2), BASE); } if (analyzeResult & SIGN) { - return parse_int((analyzeResult & NEGATIVE ? '-' : '+') + str.slice(3), BINARY); + return parse_int((analyzeResult & NEGATIVE ? '-' : '+') + str.slice(3), BASE); } - return parse_int(str.trim().slice(2), BINARY); + return parse_int(trimmedStr.slice(2), BASE); } if ((analyzeResult & EXPONENT_INDICATOR) === EXPONENT_INDICATOR) { @@ -114,14 +126,17 @@ export default function toNumber(str, options = {}) { } // If the number is out of safe integer range, return the original string - if (((analyzeResult & FLOAT) !== FLOAT) && (num > Number.MAX_SAFE_INTEGER || num < Number.MIN_SAFE_INTEGER)) { + if (((analyzeResult & FLOAT) !== FLOAT) && Number.isSafeInteger(num) === false) { return str; } if ((analyzeResult & FLOAT) === FLOAT) { + if (options.ieee754 === true) { + return num; + } const parsedDecimalPoint = parsedStr.indexOf(".") + 1; - const strDecimalPoint = str.indexOf(options.decimalPoint || ".") + 1; + const strDecimalPoint = str.indexOf(".") + 1; let i = 0; const parsedFracLength = parsedStr.length - parsedDecimalPoint; @@ -136,7 +151,33 @@ export default function toNumber(str, options = {}) { for (; i < str.length; i++) { switch (str[i]) { case "0": + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#white_space case " ": + case "\t": + case "\v": + case "\f": + case "\r": + case "\n": + case "\ufeff": // Unicode line separator + // https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5Cp%7BGeneral_Category%3DSpace_Separator%7D + case "\u00A0": // Non-breaking space + case "\u1680": // Ogham space mark + case "\u2000": // En quad + case "\u2001": // Em quad + case "\u2002": // En space + case "\u2003": // Em space + case "\u2004": // Three-per-em space + case "\u2005": // Four-per-em space + case "\u2006": // Six-per-em space + case "\u2007": // Figure space + case "\u2008": // Punctuation space + case "\u2009": // Thin space + case "\u200A": // Hair space + case "\u2028": // Line separator + case "\u2029": // Paragraph separator + case "\u202F": // Narrow no-break space + case "\u205F": // Medium mathematical space + case "\u3000": // Ideographic space continue; default: return str; @@ -147,48 +188,49 @@ export default function toNumber(str, options = {}) { return num; } -const NUMBER = /** @type {const} */ assertBitmask(0, 0); -const NOT_A_NUMBER = /** @type {const} */ assertBitmask(1, 1 << 0); +const NUMBER = /** @type {const} */ 0b00000000000000000; +const NOT_A_NUMBER = /** @type {const} */ 0b00000000000000001; -const BINARY = /** @type {const} */ assertBitmask(2, 1 << 1); -const DECIMAL = /** @type {const} */ assertBitmask(4, 1 << 2); -const OCTAL = /** @type {const} */ assertBitmask(8, 1 << 3); -const HEX = /** @type {const} */ assertBitmask(16, 1 << 4); +const BINARY = /** @type {const} */ 0b00000000000000010; +const DECIMAL = /** @type {const} */ 0b00000000000000100; +const OCTAL = /** @type {const} */ 0b00000000000001000; +const HEX = /** @type {const} */ 0b00000000000010000; -const FLOAT = /** @type {const} */ assertBitmask(32, 1 << 5); -const INTEGER = /** @type {const} */ assertBitmask(64, 1 << 6); -const BIGINT = /** @type {const} */ assertBitmask(2112, INTEGER | 1 << 11); +const FLOAT = /** @type {const} */ 0b00000000000100000; +const INTEGER = /** @type {const} */ 0b00000000001000000; +const BIGINT = /** @type {const} */ 0b00000000010000000; +const INFINITY = /** @type {const} */ 0b00000000100000000; // Special character codes -const WHITESPACE = /** @type {const} */ assertBitmask(128, 1 << 7); -const ZERO = /** @type {const} */ assertBitmask(256, 1 << 8); -const SIGN = /** @type {const} */ assertBitmask(512, 1 << 9); -const EXPONENT_INDICATOR = /** @type {const} */ assertBitmask(1024, 1 << 10); // 'e' or 'E' -const BIGINT_LITERAL_SUFFIX = /** @type {const} */ assertBitmask(2048, 1 << 11); // 'n' for BigInt +const WHITESPACE = /** @type {const} */ 0b00000001000000000; +const SIGN = /** @type {const} */ 0b00000010000000000; +const ZERO = /** @type {const} */ 0b00000100000000000; +const EXPONENT_INDICATOR = /** @type {const} */ 0b00001000000000000; // 'e' or 'E' // Positional constants -const BEGIN = /** @type {const} */ assertBitmask(2048, 1 << 11); -const END = /** @type {const} */ assertBitmask(4096, 1 << 12); +const BEGIN = /** @type {const} */ 0b00010000000000000; +const END = /** @type {const} */ 0b00100000000000000; + +const NEGATIVE = /** @type {const} */ 0b01000000000000000; -const LEADING_WHITESPACE = /** @type {const} */ assertBitmask(2176, WHITESPACE | BEGIN); -const TRAILING_WHITESPACE = /** @type {const} */ assertBitmask(4224, WHITESPACE | END); +const LEADING_WHITESPACE = /** @type {const} */ assertBitmask(8704, BEGIN | WHITESPACE); +const TRAILING_WHITESPACE = /** @type {const} */ assertBitmask(16896, END | WHITESPACE); -const BEGIN_INTEGER_DIGITS = /** @type {const} */ assertBitmask(2052, DECIMAL | BEGIN); -const BEGIN_FRAC_DIGITS = /** @type {const} */ assertBitmask(2080, FLOAT | BEGIN); -const BEGIN_HEX = /** @type {const} */ assertBitmask(2064, HEX | BEGIN); -const BEGIN_OCTAL = /** @type {const} */ assertBitmask(2056, OCTAL | BEGIN); -const BEGIN_BINARY = /** @type {const} */ assertBitmask(2050, BINARY | BEGIN); +const BEGIN_INTEGER_DIGITS = /** @type {const} */ assertBitmask(8196, BEGIN | DECIMAL); +const BEGIN_FRAC_DIGITS = /** @type {const} */ assertBitmask(8224, BEGIN | FLOAT); +const BEGIN_HEX = /** @type {const} */ assertBitmask(8208, BEGIN | HEX); +const BEGIN_OCTAL = /** @type {const} */ assertBitmask(8200, BEGIN | OCTAL); +const BEGIN_BINARY = /** @type {const} */ assertBitmask(8194, BEGIN | BINARY); -const BEGIN_EXPONENT = /** @type {const} */ assertBitmask(3072, EXPONENT_INDICATOR | BEGIN); -const EXPONENT_SIGN = /** @type {const} */ assertBitmask(1536, EXPONENT_INDICATOR | SIGN); -const EXPONENT_INTEGER = /** @type {const} */ assertBitmask(1088, EXPONENT_INDICATOR | INTEGER); +const BEGIN_EXPONENT = /** @type {const} */ assertBitmask(12288, EXPONENT_INDICATOR | BEGIN); +const EXPONENT_SIGN = /** @type {const} */ assertBitmask(5120, EXPONENT_INDICATOR | SIGN); +const EXPONENT_INTEGER = /** @type {const} */ assertBitmask(4160, EXPONENT_INDICATOR | INTEGER); -const FIRST_DIGIT_ZERO = /** @type {const} */ assertBitmask(2304, ZERO | BEGIN); -const FIRST_DIGIT_ZERO_NOT_LEADING = /** @type {const} */ assertBitmask(6400, ZERO | BEGIN | END); -const LEADING_ZEROS = /** @type {const} */ assertBitmask(2308, ZERO | BEGIN | DECIMAL); +const BEGIN_ZERO = /** @type {const} */ assertBitmask(10240, BEGIN | ZERO); +const FIRST_DIGIT_ZERO_NOT_LEADING = /** @type {const} */ assertBitmask(26624, ZERO | BEGIN | END); +const LEADING_ZEROS = /** @type {const} */ assertBitmask(10244, ZERO | BEGIN | DECIMAL); -const INFINITY = /** @type {const} */ assertBitmask(8192, 1 << 13); -const NEGATIVE = /** @type {const} */ assertBitmask(16384, 1 << 14); +const REMOVE_TYPE_HINT = /** @type {const} */ assertBitmask(10, BINARY | OCTAL); /** * @typedef {typeof NUMBER | @@ -200,7 +242,6 @@ const NEGATIVE = /** @type {const} */ assertBitmask(16384, 1 << 14); * typeof FLOAT | * typeof INTEGER | * typeof BIGINT | - * typeof BIGINT_LITERAL_SUFFIX | * typeof ZERO | * typeof WHITESPACE | * typeof BEGIN | @@ -213,7 +254,7 @@ const NEGATIVE = /** @type {const} */ assertBitmask(16384, 1 << 14); * typeof BEGIN_HEX | * typeof BEGIN_OCTAL | * typeof BEGIN_EXPONENT | - * typeof FIRST_DIGIT_ZERO | + * typeof BEGIN_ZERO | * typeof FIRST_DIGIT_ZERO_NOT_LEADING | * typeof LEADING_ZEROS | * typeof INFINITY | @@ -252,14 +293,14 @@ export function analyzeNumber(str, options) { let result = NUMBER; - const DECIMAL_POINT = options.decimalPoint || "\."; const ON_HEX = options.hex !== false ? BEGIN_HEX : NOT_A_NUMBER; const ON_E = options.eNotation !== false ? BEGIN_EXPONENT : NOT_A_NUMBER; - const ON_BIGINT = options.bigint === true ? BIGINT_LITERAL_SUFFIX : NOT_A_NUMBER; + const ON_BIGINT = options.bigint === true ? BIGINT : NOT_A_NUMBER; const ON_BINARY = options.binary === true ? BEGIN_BINARY : NOT_A_NUMBER; const ON_OCTAL = options.octal === true ? BEGIN_OCTAL : NOT_A_NUMBER; - const ON_LEADING_ZEROS = options.leadingZeros === false ? FIRST_DIGIT_ZERO_NOT_LEADING : FIRST_DIGIT_ZERO; + const ON_LEADING_ZEROS = options.leadingZeros === false ? FIRST_DIGIT_ZERO_NOT_LEADING : BEGIN_ZERO; const ON_INFINITY = options.infinity === true ? INFINITY : NOT_A_NUMBER; + const ON_EMPTY = options.empty === true ? ZERO : NOT_A_NUMBER; while (++pos < len) { switch (str[pos]) { @@ -267,7 +308,7 @@ export function analyzeNumber(str, options) { switch (state) { case FIRST_DIGIT_ZERO_NOT_LEADING: return NOT_A_NUMBER; - case FIRST_DIGIT_ZERO: + case BEGIN_ZERO: state = LEADING_ZEROS; continue; case LEADING_WHITESPACE: @@ -319,7 +360,7 @@ export function analyzeNumber(str, options) { case EXPONENT_INTEGER: continue; case SIGN: - case FIRST_DIGIT_ZERO: + case BEGIN_ZERO: case LEADING_ZEROS: case BEGIN: case LEADING_WHITESPACE: @@ -366,7 +407,7 @@ export function analyzeNumber(str, options) { state = HEX; case HEX: continue; - case FIRST_DIGIT_ZERO: + case BEGIN_ZERO: case FIRST_DIGIT_ZERO_NOT_LEADING: state = ON_BINARY; continue; @@ -380,7 +421,7 @@ export function analyzeNumber(str, options) { result |= HEX; state = HEX; continue; - case FIRST_DIGIT_ZERO: + case BEGIN_ZERO: case FIRST_DIGIT_ZERO_NOT_LEADING: case DECIMAL: case BEGIN_FRAC_DIGITS: @@ -414,12 +455,12 @@ export function analyzeNumber(str, options) { default: return NOT_A_NUMBER; } - case DECIMAL_POINT: + case ".": switch (state) { case BEGIN: case LEADING_WHITESPACE: case SIGN: - case FIRST_DIGIT_ZERO: + case BEGIN_ZERO: case FIRST_DIGIT_ZERO_NOT_LEADING: case LEADING_ZEROS: case DECIMAL: @@ -431,7 +472,7 @@ export function analyzeNumber(str, options) { case "x": case "X": switch (state) { - case FIRST_DIGIT_ZERO: + case BEGIN_ZERO: case FIRST_DIGIT_ZERO_NOT_LEADING: state = ON_HEX; continue; @@ -441,7 +482,7 @@ export function analyzeNumber(str, options) { case "o": case "O": switch (state) { - case FIRST_DIGIT_ZERO: + case BEGIN_ZERO: case FIRST_DIGIT_ZERO_NOT_LEADING: state = ON_OCTAL; continue; @@ -515,7 +556,7 @@ export function analyzeNumber(str, options) { case DECIMAL: case HEX: case EXPONENT_INTEGER: - case BIGINT_LITERAL_SUFFIX: + case BIGINT: case FLOAT: case INFINITY: result |= WHITESPACE; @@ -532,21 +573,26 @@ export function analyzeNumber(str, options) { } switch (state) { + case BEGIN_ZERO: + case FIRST_DIGIT_ZERO_NOT_LEADING: + case LEADING_ZEROS: + result |= ZERO; + case EXPONENT_INTEGER: case BINARY: case OCTAL: case DECIMAL: case HEX: case FLOAT: case LEADING_ZEROS: - case BIGINT_LITERAL_SUFFIX: - case EXPONENT_INTEGER: - case FIRST_DIGIT_ZERO: - case FIRST_DIGIT_ZERO_NOT_LEADING: - case LEADING_ZEROS: + case BIGINT: case BEGIN_FRAC_DIGITS: case TRAILING_WHITESPACE: case INFINITY: return result; + case BEGIN: + case LEADING_WHITESPACE: + result |= ON_EMPTY; + return result; default: return NOT_A_NUMBER; } From c05c77af13a2d09f40a6349b9276f514ac240f24 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Thu, 24 Jul 2025 20:05:01 +0200 Subject: [PATCH 28/36] improve --- strnum.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/strnum.js b/strnum.js index 3204cf3..d5ab2b7 100644 --- a/strnum.js +++ b/strnum.js @@ -224,7 +224,7 @@ const BEGIN_BINARY = /** @type {const} */ assertBitmask(8194, BE const BEGIN_EXPONENT = /** @type {const} */ assertBitmask(12288, EXPONENT_INDICATOR | BEGIN); const EXPONENT_SIGN = /** @type {const} */ assertBitmask(5120, EXPONENT_INDICATOR | SIGN); -const EXPONENT_INTEGER = /** @type {const} */ assertBitmask(4160, EXPONENT_INDICATOR | INTEGER); +const EXPONENT_DECIMAL = /** @type {const} */ assertBitmask(4100, EXPONENT_INDICATOR | DECIMAL); const BEGIN_ZERO = /** @type {const} */ assertBitmask(10240, BEGIN | ZERO); const FIRST_DIGIT_ZERO_NOT_LEADING = /** @type {const} */ assertBitmask(26624, ZERO | BEGIN | END); @@ -261,7 +261,7 @@ const REMOVE_TYPE_HINT = /** @type {const} */ assertBitmask(10, BINA * typeof SIGN | * typeof EXPONENT_INDICATOR | * typeof EXPONENT_SIGN | - * typeof EXPONENT_INTEGER + * typeof EXPONENT_DECIMAL * } State */ @@ -357,7 +357,7 @@ export function analyzeNumber(str, options) { length = 0; case DECIMAL: case HEX: - case EXPONENT_INTEGER: + case EXPONENT_DECIMAL: continue; case SIGN: case BEGIN_ZERO: @@ -372,8 +372,8 @@ export function analyzeNumber(str, options) { continue; case BEGIN_EXPONENT: case EXPONENT_SIGN: - result |= EXPONENT_INTEGER; - state = EXPONENT_INTEGER; + result |= EXPONENT_DECIMAL; + state = EXPONENT_DECIMAL; continue; case BEGIN_FRAC_DIGITS: result |= FLOAT; @@ -555,7 +555,7 @@ export function analyzeNumber(str, options) { case OCTAL: case DECIMAL: case HEX: - case EXPONENT_INTEGER: + case EXPONENT_DECIMAL: case BIGINT: case FLOAT: case INFINITY: @@ -577,13 +577,12 @@ export function analyzeNumber(str, options) { case FIRST_DIGIT_ZERO_NOT_LEADING: case LEADING_ZEROS: result |= ZERO; - case EXPONENT_INTEGER: + case EXPONENT_DECIMAL: case BINARY: case OCTAL: case DECIMAL: case HEX: case FLOAT: - case LEADING_ZEROS: case BIGINT: case BEGIN_FRAC_DIGITS: case TRAILING_WHITESPACE: From b72593402c9a252bb2c03b1b4b6ab23e419d6ba8 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Thu, 24 Jul 2025 20:17:55 +0200 Subject: [PATCH 29/36] reorder --- strnum.js | 42 ++++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/strnum.js b/strnum.js index d5ab2b7..d9a984f 100644 --- a/strnum.js +++ b/strnum.js @@ -306,6 +306,14 @@ export function analyzeNumber(str, options) { switch (str[pos]) { case "0": switch (state) { + case FLOAT: + ++length; + case DECIMAL: + case HEX: + case BINARY: + case OCTAL: + case BEGIN_FRAC_DIGITS: + continue; case FIRST_DIGIT_ZERO_NOT_LEADING: return NOT_A_NUMBER; case BEGIN_ZERO: @@ -316,14 +324,6 @@ export function analyzeNumber(str, options) { case SIGN: state = ON_LEADING_ZEROS; continue; - case FLOAT: - ++length; - case BEGIN_FRAC_DIGITS: - case BINARY: - case OCTAL: - case DECIMAL: - case HEX: - continue; case BEGIN_BINARY: result |= BINARY; state = BINARY; @@ -331,10 +331,11 @@ export function analyzeNumber(str, options) { } case "1": switch (state) { + case BINARY: + continue; case BEGIN_BINARY: result |= BINARY; state = BINARY; - case BINARY: continue; } case "2": @@ -344,10 +345,11 @@ export function analyzeNumber(str, options) { case "6": case "7": switch (state) { + case OCTAL: + continue; case BEGIN_OCTAL: result |= OCTAL; state = OCTAL; - case OCTAL: continue; } case "8": @@ -372,7 +374,6 @@ export function analyzeNumber(str, options) { continue; case BEGIN_EXPONENT: case EXPONENT_SIGN: - result |= EXPONENT_DECIMAL; state = EXPONENT_DECIMAL; continue; case BEGIN_FRAC_DIGITS: @@ -391,10 +392,11 @@ export function analyzeNumber(str, options) { case "D": case "F": switch (state) { + case HEX: + continue; case BEGIN_HEX: result |= HEX; state = HEX; - case HEX: continue; default: return NOT_A_NUMBER; @@ -402,10 +404,11 @@ export function analyzeNumber(str, options) { case "b": case "B": switch (state) { + case HEX: + continue; case BEGIN_HEX: result |= HEX; state = HEX; - case HEX: continue; case BEGIN_ZERO: case FIRST_DIGIT_ZERO_NOT_LEADING: @@ -417,6 +420,8 @@ export function analyzeNumber(str, options) { case "e": case "E": switch (state) { + case HEX: + continue; case BEGIN_HEX: result |= HEX; state = HEX; @@ -428,7 +433,6 @@ export function analyzeNumber(str, options) { case FLOAT: result |= EXPONENT_INDICATOR; state = ON_E; - case HEX: continue; default: return NOT_A_NUMBER; @@ -441,6 +445,11 @@ export function analyzeNumber(str, options) { result |= NEGATIVE; state = SIGN; continue; + case BEGIN_EXPONENT: + state = EXPONENT_SIGN; + continue; + default: + return NOT_A_NUMBER; } case "+": switch (state) { @@ -547,6 +556,9 @@ export function analyzeNumber(str, options) { case "\u205F": // Medium mathematical space case "\u3000": // Ideographic space switch (state) { + case LEADING_WHITESPACE: + case TRAILING_WHITESPACE: + continue; case BEGIN: result |= WHITESPACE; state = LEADING_WHITESPACE; @@ -561,8 +573,6 @@ export function analyzeNumber(str, options) { case INFINITY: result |= WHITESPACE; state = TRAILING_WHITESPACE; - case LEADING_WHITESPACE: - case TRAILING_WHITESPACE: continue; default: return NOT_A_NUMBER; From 93f47a17f1f4dc5c57f770cfbabf2b2d3865eabe Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Thu, 24 Jul 2025 21:00:02 +0200 Subject: [PATCH 30/36] improve more --- strnum.js | 45 +++++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/strnum.js b/strnum.js index d9a984f..a0b3578 100644 --- a/strnum.js +++ b/strnum.js @@ -63,7 +63,6 @@ export default function toNumber(str, options = {}) { const analyzeResult = analyzeNumber(str, options); if ((analyzeResult & NOT_A_NUMBER) === NOT_A_NUMBER) { - if (options.NaN === true) { return NaN; } @@ -85,25 +84,30 @@ export default function toNumber(str, options = {}) { return 0; } - if ((analyzeResult & HEX) === HEX) { - return parse_int(str, HEX); + if ((analyzeResult & INFINITY) === INFINITY) { + return analyzeResult & NEGATIVE ? -Infinity : Infinity; } - if ((analyzeResult & REMOVE_TYPE_HINT) !== 0) { - const BASE = /** @type {2|8} */ (analyzeResult & OCTAL || analyzeResult & BINARY); - trimmedStr = trimmedStr || ((analyzeResult & WHITESPACE) === WHITESPACE) - ? str.trim() - : str; - if ((analyzeResult & WHITESPACE) === WHITESPACE) { - if (analyzeResult & SIGN) { - return parse_int((analyzeResult & NEGATIVE ? '-' : '+') + trimmedStr.slice(3), BASE); + let num; + if ((analyzeResult & SIGN) === 0) { + num = +str; + } else if ((analyzeResult & HEX) === HEX) { + num = parse_int(str, 16); + } else if ((analyzeResult & REMOVE_TYPE_HINT) !== 0) { + if (trimmedStr === undefined) { + if ((analyzeResult & WHITESPACE) === WHITESPACE) { + trimmedStr = str.trim(); + } else { + trimmedStr = str; } - return parse_int(trimmedStr.slice(2), BASE); } - if (analyzeResult & SIGN) { - return parse_int((analyzeResult & NEGATIVE ? '-' : '+') + str.slice(3), BASE); + num = +trimmedStr.slice(1); + if (analyzeResult & NEGATIVE) { + num = -num; } - return parse_int(trimmedStr.slice(2), BASE); + return num; + } else { + num = (analyzeResult & INTEGER) === INTEGER ? parse_int(str, 10) : +str; } if ((analyzeResult & EXPONENT_INDICATOR) === EXPONENT_INDICATOR) { @@ -113,11 +117,6 @@ export default function toNumber(str, options = {}) { return str; } - if ((analyzeResult & INFINITY) === INFINITY) { - return analyzeResult & NEGATIVE ? -Infinity : Infinity; - } - - const num = (analyzeResult & INTEGER) === INTEGER ? parse_int(str, 10) : +str; const parsedStr = '' + num; if (parsedStr.indexOf(EXP_CHAR) !== -1) { @@ -136,6 +135,12 @@ export default function toNumber(str, options = {}) { } const parsedDecimalPoint = parsedStr.indexOf(".") + 1; + // If the parsed number has fewer than 14 digits after the decimal point, + // we can safely return it as a number. + if ((parsedStr.length - parsedDecimalPoint) < 14) { + return num; + } + const strDecimalPoint = str.indexOf(".") + 1; let i = 0; From 7d504aac079d7cb1edffb45661bde2ceb39a9ef2 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Thu, 24 Jul 2025 21:49:22 +0200 Subject: [PATCH 31/36] cut some corners --- strnum.js | 42 ++++++++++++++++++++---------------------- strnum.test.js | 4 ++-- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/strnum.js b/strnum.js index a0b3578..1060ace 100644 --- a/strnum.js +++ b/strnum.js @@ -5,6 +5,7 @@ * @property {boolean} [binary=false] - Whether to allow binary numbers (e.g., "0b1010"). * @property {boolean} [bigint=false] - Whether to allow BigInt numbers (e.g., "123n"). * @property {boolean} [leadingZeros=true] - Whether to allow leading zeros in numbers (e.g., "000123"). + * @property {boolean} [safeInteger=true] - Whether to check if the number is a safe integer. * @property {boolean} [infinity=false] - Whether to allow "Infinity" and "-Infinity". * @property {RegExp} [skipLike] - A regular expression to skip certain string patterns. * @property {boolean} [eNotation=true] - Whether to allow scientific notation (e.g., "1e10"). @@ -70,7 +71,6 @@ export default function toNumber(str, options = {}) { } let trimmedStr; - if (options.skipLike !== undefined) { trimmedStr = ((analyzeResult & WHITESPACE) === WHITESPACE) ? str.trim() @@ -105,46 +105,45 @@ export default function toNumber(str, options = {}) { if (analyzeResult & NEGATIVE) { num = -num; } - return num; } else { - num = (analyzeResult & INTEGER) === INTEGER ? parse_int(str, 10) : +str; + num = +str; } if ((analyzeResult & EXPONENT_INDICATOR) === EXPONENT_INDICATOR) { - if (options.eNotation !== false) { - return +str; - } - return str; - } - - const parsedStr = '' + num; - - if (parsedStr.indexOf(EXP_CHAR) !== -1) { - if (options.eNotation !== false) return num; - else return str; + return num; } // If the number is out of safe integer range, return the original string - if (((analyzeResult & FLOAT) !== FLOAT) && Number.isSafeInteger(num) === false) { - return str; - } + if (((analyzeResult & FLOAT) !== FLOAT)) { + if (options.safeInteger !== false && Number.isSafeInteger(num) === false) { + return str; + } + + if (options.eNotation === false && ('' + num).indexOf(EXP_CHAR) !== -1) { + // If the number is in scientific notation, return the original string + return str; + } - if ((analyzeResult & FLOAT) === FLOAT) { + return num; + } else { if (options.ieee754 === true) { return num; } + + const parsedStr = '' + num; const parsedDecimalPoint = parsedStr.indexOf(".") + 1; + const parsedStrLength = parsedStr.length; // If the parsed number has fewer than 14 digits after the decimal point, // we can safely return it as a number. - if ((parsedStr.length - parsedDecimalPoint) < 14) { + if ((parsedStrLength - parsedDecimalPoint) < 14) { return num; } const strDecimalPoint = str.indexOf(".") + 1; let i = 0; - const parsedFracLength = parsedStr.length - parsedDecimalPoint; + const parsedFracLength = parsedStrLength - parsedDecimalPoint; for (; i < parsedFracLength; i++) { if (parsedStr[parsedDecimalPoint + i] !== str[strDecimalPoint + i]) { return str; @@ -188,9 +187,8 @@ export default function toNumber(str, options = {}) { return str; } } + return num; } - - return num; } const NUMBER = /** @type {const} */ 0b00000000000000000; diff --git a/strnum.test.js b/strnum.test.js index 0cca763..eaac93e 100644 --- a/strnum.test.js +++ b/strnum.test.js @@ -57,7 +57,7 @@ describe("Should convert all the valid numeric strings to number", () => { expect(toNumber("000000000000000000000000017717" , { leadingZeros : false})).toEqual("000000000000000000000000017717"); expect(toNumber("000000000000000000000000017717" , { leadingZeros : true})).toEqual(17717); expect(toNumber("020211201030005811824") ).toEqual("020211201030005811824"); - expect(toNumber("0420926189200190257681175017717") ).toEqual(4.209261892001902e+29); + expect(toNumber("0420926189200190257681175017717", { safeInteger: false }) ).toEqual(4.209261892001902e+29); }) it("invalid floating number", () => { expect(toNumber("20.21.030") ).toEqual("20.21.030"); @@ -128,7 +128,7 @@ describe("Should convert all the valid numeric strings to number", () => { expect(toNumber("-1.0e2") ).toEqual(-100); expect(toNumber("1.0e-2")).toEqual(0.01); - expect(toNumber("420926189200190257681175017717") ).toEqual(4.209261892001902e+29); + expect(toNumber("420926189200190257681175017717", { safeInteger: false }) ).toEqual(4.209261892001902e+29); expect(toNumber("420926189200190257681175017717" , { eNotation: false} )).toEqual("420926189200190257681175017717"); expect(toNumber("1e-2")).toEqual(0.01); From d71ce54ed2cdd7f39fa84bbaa145d0d4d9d92d19 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Thu, 24 Jul 2025 22:07:44 +0200 Subject: [PATCH 32/36] remove stuff again --- strnum.js | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/strnum.js b/strnum.js index 1060ace..4054211 100644 --- a/strnum.js +++ b/strnum.js @@ -10,9 +10,6 @@ * @property {RegExp} [skipLike] - A regular expression to skip certain string patterns. * @property {boolean} [eNotation=true] - Whether to allow scientific notation (e.g., "1e10"). * @property {boolean} [empty=false] - Whether to treat empty strings or strings with whitespace as zero (e.g., " "). - * @property {boolean} [NaN=false] - Whether to return NaN for non-numeric values, (e.g., "abc" => NaN). - * @property {boolean} [boolean=false] - Whether to convert boolean values to numbers (true to 1, false to 0). - * @property {boolean} [null=false] - Whether to convert null to 0. * @property {boolean} [ieee754=false] - Whether to force IEEE 754 compliance for floating-point numbers (e.g. "1234567890.1234567890" => 1234567890.1234567). */ @@ -50,23 +47,13 @@ const parse_int = ((function parse_int() { * @returns {number|T} - The converted number or the original value if conversion is not applicable. */ export default function toNumber(str, options = {}) { - if (typeof str !== "string") { - if (options.boolean === true) { - if (str === true) return 1; - else if (str === false) return 0; - } - if (options.null === true) { - if (str === null) return 0; - } + if (!str || typeof str !== "string") { return str; } const analyzeResult = analyzeNumber(str, options); if ((analyzeResult & NOT_A_NUMBER) === NOT_A_NUMBER) { - if (options.NaN === true) { - return NaN; - } return str; } @@ -303,7 +290,6 @@ export function analyzeNumber(str, options) { const ON_OCTAL = options.octal === true ? BEGIN_OCTAL : NOT_A_NUMBER; const ON_LEADING_ZEROS = options.leadingZeros === false ? FIRST_DIGIT_ZERO_NOT_LEADING : BEGIN_ZERO; const ON_INFINITY = options.infinity === true ? INFINITY : NOT_A_NUMBER; - const ON_EMPTY = options.empty === true ? ZERO : NOT_A_NUMBER; while (++pos < len) { switch (str[pos]) { @@ -601,10 +587,6 @@ export function analyzeNumber(str, options) { case TRAILING_WHITESPACE: case INFINITY: return result; - case BEGIN: - case LEADING_WHITESPACE: - result |= ON_EMPTY; - return result; default: return NOT_A_NUMBER; } From 146735291a2e0dedda7c7d2ce2eb8f49efdc4851 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Fri, 25 Jul 2025 02:46:59 +0200 Subject: [PATCH 33/36] improve --- strnum.d.ts | 53 +++++++++++++++++++++----------------------------- strnum.js | 6 ++++-- strnum.test.js | 5 +++++ 3 files changed, 31 insertions(+), 33 deletions(-) diff --git a/strnum.d.ts b/strnum.d.ts index ca235b8..7727c9f 100644 --- a/strnum.d.ts +++ b/strnum.d.ts @@ -12,7 +12,7 @@ declare module "strnum" { * @returns {number} - A bitmask representing the analysis result of the string. */ export function analyzeNumber(str: string, options: Options): number; - export type State = typeof NUMBER | typeof NOT_A_NUMBER | typeof BINARY | typeof DECIMAL | typeof OCTAL | typeof HEX | typeof FLOAT | typeof INTEGER | typeof BIGINT | typeof BIGINT_LITERAL_SUFFIX | typeof ZERO | typeof WHITESPACE | typeof BEGIN | typeof END | typeof LEADING_WHITESPACE | typeof TRAILING_WHITESPACE | typeof BEGIN_INTEGER_DIGITS | typeof BEGIN_FRAC_DIGITS | typeof BEGIN_BINARY | typeof BEGIN_HEX | typeof BEGIN_OCTAL | typeof BEGIN_EXPONENT | typeof BEGIN_ZERO | typeof FIRST_DIGIT_ZERO_NOT_LEADING | typeof LEADING_ZEROS | typeof INFINITY | typeof SIGN | typeof EXPONENT_INDICATOR | typeof EXPONENT_SIGN | typeof EXPONENT_INTEGER; + export type State = typeof NUMBER | typeof NOT_A_NUMBER | typeof BINARY | typeof DECIMAL | typeof OCTAL | typeof HEX | typeof FLOAT | typeof INTEGER | typeof BIGINT | typeof ZERO | typeof WHITESPACE | typeof BEGIN | typeof END | typeof LEADING_WHITESPACE | typeof TRAILING_WHITESPACE | typeof BEGIN_INTEGER_DIGITS | typeof BEGIN_FRAC_DIGITS | typeof BEGIN_BINARY | typeof BEGIN_HEX | typeof BEGIN_OCTAL | typeof BEGIN_EXPONENT | typeof BEGIN_ZERO | typeof FIRST_DIGIT_ZERO_NOT_LEADING | typeof LEADING_ZEROS | typeof INFINITY | typeof SIGN | typeof EXPONENT_INDICATOR | typeof EXPONENT_SIGN | typeof EXPONENT_DECIMAL; export type Options = { /** * - Whether to allow hexadecimal numbers (e.g., "0x1A"). @@ -31,9 +31,13 @@ declare module "strnum" { */ bigint?: boolean; /** - * - Whether to allow leading zeros in numbers. + * - Whether to allow leading zeros in numbers (e.g., "000123"). */ leadingZeros?: boolean; + /** + * - Whether to check if the number is a safe integer. + */ + safeInteger?: boolean; /** * - Whether to allow "Infinity" and "-Infinity". */ @@ -47,23 +51,11 @@ declare module "strnum" { */ eNotation?: boolean; /** - * - Whether to treat empty strings or strings with whitespace as zero. + * - Whether to treat empty strings or strings with whitespace as zero (e.g., " "). */ empty?: boolean; /** - * - Whether to return NaN for non-numeric values. - */ - NaN?: boolean; - /** - * - Whether to convert boolean values to numbers (true to 1, false to 0). - */ - boolean?: boolean; - /** - * - Whether to convert null to 0. - */ - null?: boolean; - /** - * - Whether to force IEEE 754 compliance for floating-point numbers. + * - Whether to force IEEE 754 compliance for floating-point numbers (e.g. "1234567890.1234567890" => 1234567890.1234567). */ ieee754?: boolean; }; @@ -76,26 +68,25 @@ declare module "strnum" { const FLOAT: 32; const INTEGER: 64; const BIGINT: 128; - const BIGINT_LITERAL_SUFFIX: 8192; const ZERO: 2048; const WHITESPACE: 512; - const BEGIN: 16384; - const END: 32768; - const LEADING_WHITESPACE: 16896; - const TRAILING_WHITESPACE: 33280; - const BEGIN_INTEGER_DIGITS: 16388; - const BEGIN_FRAC_DIGITS: 16416; - const BEGIN_BINARY: 16386; - const BEGIN_HEX: 16400; - const BEGIN_OCTAL: 16392; - const BEGIN_EXPONENT: 20480; - const BEGIN_ZERO: 18432; - const FIRST_DIGIT_ZERO_NOT_LEADING: 51200; - const LEADING_ZEROS: 18436; + const BEGIN: 8192; + const END: 16384; + const LEADING_WHITESPACE: 8704; + const TRAILING_WHITESPACE: 16896; + const BEGIN_INTEGER_DIGITS: 8196; + const BEGIN_FRAC_DIGITS: 8224; + const BEGIN_BINARY: 8194; + const BEGIN_HEX: 8208; + const BEGIN_OCTAL: 8200; + const BEGIN_EXPONENT: 12288; + const BEGIN_ZERO: 10240; + const FIRST_DIGIT_ZERO_NOT_LEADING: 26624; + const LEADING_ZEROS: 10244; const INFINITY: 256; const SIGN: 1024; const EXPONENT_INDICATOR: 4096; const EXPONENT_SIGN: 5120; - const EXPONENT_INTEGER: 4160; + const EXPONENT_DECIMAL: 4100; export {}; } diff --git a/strnum.js b/strnum.js index 4054211..1220265 100644 --- a/strnum.js +++ b/strnum.js @@ -30,7 +30,7 @@ const EXP_CHAR = function () { } }(); -/** @type {(string: string, radix: 2|8|10|16) => number} */ +/** @type {(string: string, radix?: 2|8|10|16) => number} */ const parse_int = ((function parse_int() { if (parseInt) return parseInt; else if (Number.parseInt) return Number.parseInt; @@ -76,7 +76,9 @@ export default function toNumber(str, options = {}) { } let num; - if ((analyzeResult & SIGN) === 0) { + if ((analyzeResult & BIGINT) === BIGINT) { + num = parse_int(str); + } else if ((analyzeResult & SIGN) === 0) { num = +str; } else if ((analyzeResult & HEX) === HEX) { num = parse_int(str, 16); diff --git a/strnum.test.js b/strnum.test.js index eaac93e..f43078a 100644 --- a/strnum.test.js +++ b/strnum.test.js @@ -200,4 +200,9 @@ describe("Should convert all the valid numeric strings to number", () => { expect(toNumber(" -Infinity ", { infinity: true })).toEqual(-Infinity); expect(toNumber(" +Infinity ", { infinity: true })).toEqual(Infinity); }) + + it("bigint", () => { + expect(toNumber("1212n", { bigint: true })).toEqual(1212); + expect(toNumber("-1212n", { bigint: true })).toEqual(-1212); + }) }); From 1facfc29766716f2884d4b69967be1af59ec749f Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Fri, 25 Jul 2025 02:55:52 +0200 Subject: [PATCH 34/36] use codepoint --- strnum.js | 170 +++++++++++++++++++++++++++--------------------------- 1 file changed, 85 insertions(+), 85 deletions(-) diff --git a/strnum.js b/strnum.js index 1220265..874887b 100644 --- a/strnum.js +++ b/strnum.js @@ -78,7 +78,7 @@ export default function toNumber(str, options = {}) { let num; if ((analyzeResult & BIGINT) === BIGINT) { num = parse_int(str); - } else if ((analyzeResult & SIGN) === 0) { + } else if ((analyzeResult & SIGN) === 0) { num = +str; } else if ((analyzeResult & HEX) === HEX) { num = parse_int(str, 16); @@ -142,35 +142,35 @@ export default function toNumber(str, options = {}) { // ignore trailing zeros and whitespace in the fractional part i += strDecimalPoint; for (; i < str.length; i++) { - switch (str[i]) { - case "0": + switch (str.codePointAt(i)) { + case 0x30: // '0' // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#white_space - case " ": - case "\t": - case "\v": - case "\f": - case "\r": - case "\n": - case "\ufeff": // Unicode line separator + case 0x20: // ' ' + case 0x09: // '\t' + case 0x0b: // '\v' + case 0x0c: // '\f' + case 0x0d: // '\r' + case 0x0a: // '\n' + case 0xFEFF: // '\ufeff' (Unicode line separator) // https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5Cp%7BGeneral_Category%3DSpace_Separator%7D - case "\u00A0": // Non-breaking space - case "\u1680": // Ogham space mark - case "\u2000": // En quad - case "\u2001": // Em quad - case "\u2002": // En space - case "\u2003": // Em space - case "\u2004": // Three-per-em space - case "\u2005": // Four-per-em space - case "\u2006": // Six-per-em space - case "\u2007": // Figure space - case "\u2008": // Punctuation space - case "\u2009": // Thin space - case "\u200A": // Hair space - case "\u2028": // Line separator - case "\u2029": // Paragraph separator - case "\u202F": // Narrow no-break space - case "\u205F": // Medium mathematical space - case "\u3000": // Ideographic space + case 0xA0: // Non-breaking space + case 0x1680: // Ogham space mark + case 0x2000: // En quad + case 0x2001: // Em quad + case 0x2002: // En space + case 0x2003: // Em space + case 0x2004: // Three-per-em space + case 0x2005: // Four-per-em space + case 0x2006: // Six-per-em space + case 0x2007: // Figure space + case 0x2008: // Punctuation space + case 0x2009: // Thin space + case 0x200A: // Hair space + case 0x2028: // Line separator + case 0x2029: // Paragraph separator + case 0x202F: // Narrow no-break space + case 0x205F: // Medium mathematical space + case 0x3000: // Ideographic space continue; default: return str; @@ -294,8 +294,8 @@ export function analyzeNumber(str, options) { const ON_INFINITY = options.infinity === true ? INFINITY : NOT_A_NUMBER; while (++pos < len) { - switch (str[pos]) { - case "0": + switch (str.codePointAt(pos)) { + case 0x30: // '0' switch (state) { case FLOAT: ++length; @@ -320,7 +320,7 @@ export function analyzeNumber(str, options) { state = BINARY; continue; } - case "1": + case 0x31: // '1' switch (state) { case BINARY: continue; @@ -329,12 +329,12 @@ export function analyzeNumber(str, options) { state = BINARY; continue; } - case "2": - case "3": - case "4": - case "5": - case "6": - case "7": + case 0x32: // '2' + case 0x33: // '3' + case 0x34: // '4' + case 0x35: // '5' + case 0x36: // '6' + case 0x37: // '7' switch (state) { case OCTAL: continue; @@ -343,8 +343,8 @@ export function analyzeNumber(str, options) { state = OCTAL; continue; } - case "8": - case "9": + case 0x38: // '8' + case 0x39: // '9' switch (state) { case FLOAT: length = 0; @@ -374,14 +374,14 @@ export function analyzeNumber(str, options) { default: return NOT_A_NUMBER; } - case "a": - case "c": - case "d": - case "f": - case "A": - case "C": - case "D": - case "F": + case 0x61: // 'a' + case 0x63: // 'c' + case 0x64: // 'd' + case 0x66: // 'f' + case 0x41: // 'A' + case 0x43: // 'C' + case 0x44: // 'D' + case 0x46: // 'F' switch (state) { case HEX: continue; @@ -392,8 +392,8 @@ export function analyzeNumber(str, options) { default: return NOT_A_NUMBER; } - case "b": - case "B": + case 0x62: // 'b' + case 0x42: // 'B' switch (state) { case HEX: continue; @@ -408,8 +408,8 @@ export function analyzeNumber(str, options) { default: return NOT_A_NUMBER; } - case "e": - case "E": + case 0x65: // 'e' + case 0x45: // 'E' switch (state) { case HEX: continue; @@ -428,7 +428,7 @@ export function analyzeNumber(str, options) { default: return NOT_A_NUMBER; } - case "-": + case 0x2D: // '-' switch (state) { case BEGIN: case LEADING_WHITESPACE: @@ -442,7 +442,7 @@ export function analyzeNumber(str, options) { default: return NOT_A_NUMBER; } - case "+": + case 0x2B: // '+' switch (state) { case BEGIN: case LEADING_WHITESPACE: @@ -455,7 +455,7 @@ export function analyzeNumber(str, options) { default: return NOT_A_NUMBER; } - case ".": + case 0x2E: // '.' switch (state) { case BEGIN: case LEADING_WHITESPACE: @@ -469,8 +469,8 @@ export function analyzeNumber(str, options) { default: return NOT_A_NUMBER; } - case "x": - case "X": + case 0x78: // 'x' + case 0x58: // 'X' switch (state) { case BEGIN_ZERO: case FIRST_DIGIT_ZERO_NOT_LEADING: @@ -479,8 +479,8 @@ export function analyzeNumber(str, options) { default: return NOT_A_NUMBER; } - case "o": - case "O": + case 0x6F: // 'o' + case 0x4F: // 'O' switch (state) { case BEGIN_ZERO: case FIRST_DIGIT_ZERO_NOT_LEADING: @@ -489,7 +489,7 @@ export function analyzeNumber(str, options) { default: return NOT_A_NUMBER; } - case "n": + case 0x6E: // 'n' switch (state) { case DECIMAL: result |= BIGINT; @@ -498,7 +498,7 @@ export function analyzeNumber(str, options) { default: return NOT_A_NUMBER; } - case "I": + case 0x49: // 'I' switch (state) { case BEGIN: case LEADING_WHITESPACE: @@ -520,32 +520,32 @@ export function analyzeNumber(str, options) { return NOT_A_NUMBER; } // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#white_space - case " ": - case "\t": - case "\v": - case "\f": - case "\r": - case "\n": - case "\ufeff": // Unicode line separator + case 0x20: // ' ' + case 0x09: // '\t' + case 0x0b: // '\v' + case 0x0c: // '\f' + case 0x0d: // '\r' + case 0x0a: // '\n' + case 0xFEFF: // '\ufeff' (Unicode line separator) // https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5Cp%7BGeneral_Category%3DSpace_Separator%7D - case "\u00A0": // Non-breaking space - case "\u1680": // Ogham space mark - case "\u2000": // En quad - case "\u2001": // Em quad - case "\u2002": // En space - case "\u2003": // Em space - case "\u2004": // Three-per-em space - case "\u2005": // Four-per-em space - case "\u2006": // Six-per-em space - case "\u2007": // Figure space - case "\u2008": // Punctuation space - case "\u2009": // Thin space - case "\u200A": // Hair space - case "\u2028": // Line separator - case "\u2029": // Paragraph separator - case "\u202F": // Narrow no-break space - case "\u205F": // Medium mathematical space - case "\u3000": // Ideographic space + case 0xA0: // Non-breaking space + case 0x1680: // Ogham space mark + case 0x2000: // En quad + case 0x2001: // Em quad + case 0x2002: // En space + case 0x2003: // Em space + case 0x2004: // Three-per-em space + case 0x2005: // Four-per-em space + case 0x2006: // Six-per-em space + case 0x2007: // Figure space + case 0x2008: // Punctuation space + case 0x2009: // Thin space + case 0x200A: // Hair space + case 0x2028: // Line separator + case 0x2029: // Paragraph separator + case 0x202F: // Narrow no-break space + case 0x205F: // Medium mathematical space + case 0x3000: // Ideographic space switch (state) { case LEADING_WHITESPACE: case TRAILING_WHITESPACE: From 7125ba10e293fbdc649dae8a25f4fd165563750e Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Fri, 25 Jul 2025 04:14:10 +0200 Subject: [PATCH 35/36] improve --- strnum.js | 72 +++++++++++++++++++++---------------------------------- 1 file changed, 27 insertions(+), 45 deletions(-) diff --git a/strnum.js b/strnum.js index 874887b..7f716ca 100644 --- a/strnum.js +++ b/strnum.js @@ -91,7 +91,7 @@ export default function toNumber(str, options = {}) { } } num = +trimmedStr.slice(1); - if (analyzeResult & NEGATIVE) { + if ((analyzeResult & NEGATIVE) === NEGATIVE) { num = -num; } } else { @@ -141,8 +141,8 @@ export default function toNumber(str, options = {}) { // ignore trailing zeros and whitespace in the fractional part i += strDecimalPoint; - for (; i < str.length; i++) { - switch (str.codePointAt(i)) { + while (i++ < str.length) { + switch (str.charCodeAt(i)) { case 0x30: // '0' // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#white_space case 0x20: // ' ' @@ -205,18 +205,17 @@ const END = /** @type {const} */ 0b00100000000000000; const NEGATIVE = /** @type {const} */ 0b01000000000000000; -const LEADING_WHITESPACE = /** @type {const} */ assertBitmask(8704, BEGIN | WHITESPACE); +const LEADING_WHITESPACE = /** @type {const} */ assertBitmask(8705, BEGIN | WHITESPACE | NOT_A_NUMBER); const TRAILING_WHITESPACE = /** @type {const} */ assertBitmask(16896, END | WHITESPACE); -const BEGIN_INTEGER_DIGITS = /** @type {const} */ assertBitmask(8196, BEGIN | DECIMAL); const BEGIN_FRAC_DIGITS = /** @type {const} */ assertBitmask(8224, BEGIN | FLOAT); -const BEGIN_HEX = /** @type {const} */ assertBitmask(8208, BEGIN | HEX); -const BEGIN_OCTAL = /** @type {const} */ assertBitmask(8200, BEGIN | OCTAL); -const BEGIN_BINARY = /** @type {const} */ assertBitmask(8194, BEGIN | BINARY); +const BEGIN_HEX = /** @type {const} */ assertBitmask(8209, BEGIN | HEX | NOT_A_NUMBER); +const BEGIN_OCTAL = /** @type {const} */ assertBitmask(8201, BEGIN | OCTAL | NOT_A_NUMBER); +const BEGIN_BINARY = /** @type {const} */ assertBitmask(8195, BEGIN | BINARY | NOT_A_NUMBER); -const BEGIN_EXPONENT = /** @type {const} */ assertBitmask(12288, EXPONENT_INDICATOR | BEGIN); -const EXPONENT_SIGN = /** @type {const} */ assertBitmask(5120, EXPONENT_INDICATOR | SIGN); -const EXPONENT_DECIMAL = /** @type {const} */ assertBitmask(4100, EXPONENT_INDICATOR | DECIMAL); +const BEGIN_EXPONENT = /** @type {const} */ assertBitmask(12289, EXPONENT_INDICATOR | BEGIN | NOT_A_NUMBER); +const EXPONENT_SIGN = /** @type {const} */ assertBitmask(5121, EXPONENT_INDICATOR | SIGN | NOT_A_NUMBER); +const EXPONENT = /** @type {const} */ assertBitmask(4100, EXPONENT_INDICATOR | DECIMAL); const BEGIN_ZERO = /** @type {const} */ assertBitmask(10240, BEGIN | ZERO); const FIRST_DIGIT_ZERO_NOT_LEADING = /** @type {const} */ assertBitmask(26624, ZERO | BEGIN | END); @@ -240,7 +239,6 @@ const REMOVE_TYPE_HINT = /** @type {const} */ assertBitmask(10, BINA * typeof END | * typeof LEADING_WHITESPACE | * typeof TRAILING_WHITESPACE | - * typeof BEGIN_INTEGER_DIGITS | * typeof BEGIN_FRAC_DIGITS | * typeof BEGIN_BINARY | * typeof BEGIN_HEX | @@ -253,7 +251,7 @@ const REMOVE_TYPE_HINT = /** @type {const} */ assertBitmask(10, BINA * typeof SIGN | * typeof EXPONENT_INDICATOR | * typeof EXPONENT_SIGN | - * typeof EXPONENT_DECIMAL + * typeof EXPONENT * } State */ @@ -280,7 +278,6 @@ export function analyzeNumber(str, options) { /** @type {State} */ let state = BEGIN; - let length = 0; let pos = -1; let result = NUMBER; @@ -294,11 +291,10 @@ export function analyzeNumber(str, options) { const ON_INFINITY = options.infinity === true ? INFINITY : NOT_A_NUMBER; while (++pos < len) { - switch (str.codePointAt(pos)) { + switch (str.charCodeAt(pos)) { case 0x30: // '0' switch (state) { case FLOAT: - ++length; case DECIMAL: case HEX: case BINARY: @@ -347,10 +343,9 @@ export function analyzeNumber(str, options) { case 0x39: // '9' switch (state) { case FLOAT: - length = 0; case DECIMAL: case HEX: - case EXPONENT_DECIMAL: + case EXPONENT: continue; case SIGN: case BEGIN_ZERO: @@ -365,7 +360,7 @@ export function analyzeNumber(str, options) { continue; case BEGIN_EXPONENT: case EXPONENT_SIGN: - state = EXPONENT_DECIMAL; + state = EXPONENT; continue; case BEGIN_FRAC_DIGITS: result |= FLOAT; @@ -504,13 +499,13 @@ export function analyzeNumber(str, options) { case LEADING_WHITESPACE: case SIGN: if ( - str[++pos] === "n" && - str[++pos] === "f" && - str[++pos] === "i" && - str[++pos] === "n" && - str[++pos] === "i" && - str[++pos] === "t" && - str[++pos] === "y" + str.charCodeAt(++pos) === 0x6E && // 'n' + str.charCodeAt(++pos) === 0x66 && // 'f' + str.charCodeAt(++pos) === 0x69 && // 'i' + str.charCodeAt(++pos) === 0x6E && // 'n' + str.charCodeAt(++pos) === 0x69 && // 'i' + str.charCodeAt(++pos) === 0x74 && // 't' + str.charCodeAt(++pos) === 0x79 // 'y' ) { result |= INFINITY; state = ON_INFINITY; @@ -558,7 +553,7 @@ export function analyzeNumber(str, options) { case OCTAL: case DECIMAL: case HEX: - case EXPONENT_DECIMAL: + case EXPONENT: case BIGINT: case FLOAT: case INFINITY: @@ -573,23 +568,10 @@ export function analyzeNumber(str, options) { } } - switch (state) { - case BEGIN_ZERO: - case FIRST_DIGIT_ZERO_NOT_LEADING: - case LEADING_ZEROS: - result |= ZERO; - case EXPONENT_DECIMAL: - case BINARY: - case OCTAL: - case DECIMAL: - case HEX: - case FLOAT: - case BIGINT: - case BEGIN_FRAC_DIGITS: - case TRAILING_WHITESPACE: - case INFINITY: - return result; - default: - return NOT_A_NUMBER; + if (state & NOT_A_NUMBER) { + return NOT_A_NUMBER; + } else if (state & ZERO) { + return result | ZERO; } + return result; } From bb8fac2df16e8b50bdd2d77f9230da188748af63 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Fri, 25 Jul 2025 04:29:01 +0200 Subject: [PATCH 36/36] fix --- strnum.js | 41 ++++++++++++++--------------------------- 1 file changed, 14 insertions(+), 27 deletions(-) diff --git a/strnum.js b/strnum.js index 7f716ca..2a0c1f2 100644 --- a/strnum.js +++ b/strnum.js @@ -208,14 +208,15 @@ const NEGATIVE = /** @type {const} */ 0b01000000000000000; const LEADING_WHITESPACE = /** @type {const} */ assertBitmask(8705, BEGIN | WHITESPACE | NOT_A_NUMBER); const TRAILING_WHITESPACE = /** @type {const} */ assertBitmask(16896, END | WHITESPACE); +const BEGIN_SIGN = /** @type {const} */ assertBitmask(9217, BEGIN | SIGN | NOT_A_NUMBER); const BEGIN_FRAC_DIGITS = /** @type {const} */ assertBitmask(8224, BEGIN | FLOAT); const BEGIN_HEX = /** @type {const} */ assertBitmask(8209, BEGIN | HEX | NOT_A_NUMBER); const BEGIN_OCTAL = /** @type {const} */ assertBitmask(8201, BEGIN | OCTAL | NOT_A_NUMBER); const BEGIN_BINARY = /** @type {const} */ assertBitmask(8195, BEGIN | BINARY | NOT_A_NUMBER); const BEGIN_EXPONENT = /** @type {const} */ assertBitmask(12289, EXPONENT_INDICATOR | BEGIN | NOT_A_NUMBER); -const EXPONENT_SIGN = /** @type {const} */ assertBitmask(5121, EXPONENT_INDICATOR | SIGN | NOT_A_NUMBER); -const EXPONENT = /** @type {const} */ assertBitmask(4100, EXPONENT_INDICATOR | DECIMAL); +const BEGIN_EXPONENT_SIGN = /** @type {const} */ assertBitmask(5121, EXPONENT_INDICATOR | SIGN | NOT_A_NUMBER); +const EXPONENT = /** @type {const} */ assertBitmask(4100, EXPONENT_INDICATOR | DECIMAL); const BEGIN_ZERO = /** @type {const} */ assertBitmask(10240, BEGIN | ZERO); const FIRST_DIGIT_ZERO_NOT_LEADING = /** @type {const} */ assertBitmask(26624, ZERO | BEGIN | END); @@ -233,10 +234,7 @@ const REMOVE_TYPE_HINT = /** @type {const} */ assertBitmask(10, BINA * typeof FLOAT | * typeof INTEGER | * typeof BIGINT | - * typeof ZERO | - * typeof WHITESPACE | * typeof BEGIN | - * typeof END | * typeof LEADING_WHITESPACE | * typeof TRAILING_WHITESPACE | * typeof BEGIN_FRAC_DIGITS | @@ -248,9 +246,9 @@ const REMOVE_TYPE_HINT = /** @type {const} */ assertBitmask(10, BINA * typeof FIRST_DIGIT_ZERO_NOT_LEADING | * typeof LEADING_ZEROS | * typeof INFINITY | - * typeof SIGN | + * typeof BEGIN_SIGN | * typeof EXPONENT_INDICATOR | - * typeof EXPONENT_SIGN | + * typeof BEGIN_EXPONENT_SIGN | * typeof EXPONENT * } State */ @@ -294,13 +292,6 @@ export function analyzeNumber(str, options) { switch (str.charCodeAt(pos)) { case 0x30: // '0' switch (state) { - case FLOAT: - case DECIMAL: - case HEX: - case BINARY: - case OCTAL: - case BEGIN_FRAC_DIGITS: - continue; case FIRST_DIGIT_ZERO_NOT_LEADING: return NOT_A_NUMBER; case BEGIN_ZERO: @@ -308,13 +299,9 @@ export function analyzeNumber(str, options) { continue; case LEADING_WHITESPACE: case BEGIN: - case SIGN: + case BEGIN_SIGN: state = ON_LEADING_ZEROS; continue; - case BEGIN_BINARY: - result |= BINARY; - state = BINARY; - continue; } case 0x31: // '1' switch (state) { @@ -347,7 +334,7 @@ export function analyzeNumber(str, options) { case HEX: case EXPONENT: continue; - case SIGN: + case BEGIN_SIGN: case BEGIN_ZERO: case LEADING_ZEROS: case BEGIN: @@ -359,7 +346,7 @@ export function analyzeNumber(str, options) { state = HEX; continue; case BEGIN_EXPONENT: - case EXPONENT_SIGN: + case BEGIN_EXPONENT_SIGN: state = EXPONENT; continue; case BEGIN_FRAC_DIGITS: @@ -429,10 +416,10 @@ export function analyzeNumber(str, options) { case LEADING_WHITESPACE: result |= SIGN; result |= NEGATIVE; - state = SIGN; + state = BEGIN_SIGN; continue; case BEGIN_EXPONENT: - state = EXPONENT_SIGN; + state = BEGIN_EXPONENT_SIGN; continue; default: return NOT_A_NUMBER; @@ -442,10 +429,10 @@ export function analyzeNumber(str, options) { case BEGIN: case LEADING_WHITESPACE: result |= SIGN; - state = SIGN; + state = BEGIN_SIGN; continue; case BEGIN_EXPONENT: - state = EXPONENT_SIGN; + state = BEGIN_EXPONENT_SIGN; continue; default: return NOT_A_NUMBER; @@ -454,7 +441,7 @@ export function analyzeNumber(str, options) { switch (state) { case BEGIN: case LEADING_WHITESPACE: - case SIGN: + case BEGIN_SIGN: case BEGIN_ZERO: case FIRST_DIGIT_ZERO_NOT_LEADING: case LEADING_ZEROS: @@ -497,7 +484,7 @@ export function analyzeNumber(str, options) { switch (state) { case BEGIN: case LEADING_WHITESPACE: - case SIGN: + case BEGIN_SIGN: if ( str.charCodeAt(++pos) === 0x6E && // 'n' str.charCodeAt(++pos) === 0x66 && // 'f'