From a0d9411aa1965fba18c853d840673b695b16dc91 Mon Sep 17 00:00:00 2001 From: Jeff Brower Date: Sun, 5 Dec 2021 20:20:13 -0500 Subject: [PATCH 1/3] Store alphabet internally as object (fixes #250 & #309) --- bignumber.js | 72 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 20 deletions(-) diff --git a/bignumber.js b/bignumber.js index 846772d..313d73f 100644 --- a/bignumber.js +++ b/bignumber.js @@ -162,7 +162,7 @@ // The alphabet used for base conversion. It must be at least 2 characters long, with no '+', // '-', '.', whitespace, or repeated character. // '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_' - ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz'; + ALPHABET = createAlphabet('0123456789abcdefghijklmnopqrstuvwxyz'); //------------------------------------------------------------------------------------------ @@ -179,8 +179,7 @@ * [b] {number} The base of v. Integer, 2 to ALPHABET.length inclusive. */ function BigNumber(v, b) { - var alphabet, c, caseChanged, e, i, isNum, len, str, - x = this; + var c, e, i, isNum, len, str, x = this; // Enable constructor call without `new`. if (!(x instanceof BigNumber)) return new BigNumber(v, b); @@ -252,7 +251,7 @@ // Allow exponential notation to be used with base 10 argument, while // also rounding to DECIMAL_PLACES as with other bases. - if (b == 10) { + if (b == 10 && ALPHABET.decimalCompatible) { x = new BigNumber(v); return round(x, DECIMAL_PLACES + x.e + 1, ROUNDING_MODE); } @@ -275,13 +274,12 @@ x.s = str.charCodeAt(0) === 45 ? (str = str.slice(1), -1) : 1; } - alphabet = ALPHABET.slice(0, b); e = i = 0; // Check that str is a valid base b number. // Don't use RegExp, so alphabet can contain special characters. for (len = str.length; i < len; i++) { - if (alphabet.indexOf(c = str.charAt(i)) < 0) { + if (ALPHABET.charIndex(c = str.charAt(i), b) < 0) { if (c == '.') { // If '.' is not the first character and it has not be found before. @@ -289,16 +287,6 @@ e = len; continue; } - } else if (!caseChanged) { - - // Allow e.g. hexadecimal 'FF' as well as 'ff'. - if (str == str.toUpperCase() && (str = str.toLowerCase()) || - str == str.toLowerCase() && (str = str.toUpperCase())) { - caseChanged = true; - i = -1; - e = 0; - continue; - } } return parseNumeric(x, String(v), isNum, b); @@ -544,7 +532,7 @@ // Disallow if less than two characters, // or if it contains '+', '-', '.', whitespace, or a repeated character. if (typeof v == 'string' && !/^.?$|[+\-.\s]|(.).*\1/.test(v)) { - ALPHABET = v; + ALPHABET = createAlphabet(v); } else { throw Error (bignumberError + p + ' invalid: ' + v); @@ -568,7 +556,7 @@ MODULO_MODE: MODULO_MODE, POW_PRECISION: POW_PRECISION, FORMAT: FORMAT, - ALPHABET: ALPHABET + ALPHABET: ALPHABET.original }; }; @@ -812,7 +800,7 @@ // Called by BigNumber and BigNumber.prototype.toString. convertBase = (function () { - var decimal = '0123456789'; + var decimal = createAlphabet('0123456789'); /* * Convert string of baseIn to an array of numbers of baseOut. @@ -829,7 +817,7 @@ for (; i < len;) { for (arrL = arr.length; arrL--; arr[arrL] *= baseIn); - arr[0] += alphabet.indexOf(str.charAt(i++)); + arr[0] += alphabet.charIndex(str.charAt(i++)); for (j = 0; j < arr.length; j++) { @@ -2877,6 +2865,50 @@ } + function createAlphabet(alphabet) { + var i, j, c, d, caseSensitive, lowerAlphabet; + + // check if alphabet is case-sensitive + for (i = 0; i < alphabet.length; i++) { + c = alphabet.charAt(i); + for (j = 0; j < i; j++) { + d = alphabet.charAt(j); + if (c === d) { + throw new Error(bignumberError + 'Duplicate character in alphabet: ' + c); + } + if (c.toLowerCase() === d.toLowerCase()) { + caseSensitive = true; + } + } + } + + if (!caseSensitive) { + lowerAlphabet = alphabet.toLowerCase(); + } + + function charAt(i) { + return alphabet.charAt(i); + } + + // case-insensitive version of indexOf + function charIndex(c, b) { + var i = caseSensitive + ? alphabet.indexOf(c) + : lowerAlphabet.indexOf(c.toLowerCase()); + + return !b || i < b ? i : -1; + } + + return { + original: alphabet, + decimalCompatible: alphabet.slice(0, 10) === '0123456789', + length: alphabet.length, + charAt: charAt, + charIndex: charIndex, + }; + } + + // EXPORT From 27a82d7c416a2c1bac66617a872a56e1151cdc5f Mon Sep 17 00:00:00 2001 From: Jeff Brower Date: Sun, 5 Dec 2021 20:22:51 -0500 Subject: [PATCH 2/3] Remove breaking change (thrown Error) --- bignumber.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/bignumber.js b/bignumber.js index 313d73f..95899fd 100644 --- a/bignumber.js +++ b/bignumber.js @@ -2873,9 +2873,6 @@ c = alphabet.charAt(i); for (j = 0; j < i; j++) { d = alphabet.charAt(j); - if (c === d) { - throw new Error(bignumberError + 'Duplicate character in alphabet: ' + c); - } if (c.toLowerCase() === d.toLowerCase()) { caseSensitive = true; } From 0fd7712fd3d67d1bb64a62e0bb730f77968572b2 Mon Sep 17 00:00:00 2001 From: Jeff Brower Date: Sun, 5 Dec 2021 20:43:08 -0500 Subject: [PATCH 3/3] Improve test cases for BigNumber constructor --- test/methods/BigNumber.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/methods/BigNumber.js b/test/methods/BigNumber.js index cc87e7a..b54709c 100644 --- a/test/methods/BigNumber.js +++ b/test/methods/BigNumber.js @@ -622,10 +622,10 @@ Test('bigNumber', function () { BigNumber.config({ALPHABET: 'xy'}); t('1', 'y', 2); // 1 - t('2', 'yx', 2); // 10 - t('3', 'yy', 2); // 11 - t('4', 'yxx', 2); // 100 - t('5', 'yxy', 2); // 101 + t('2', 'Yx', 2); // 10 + t('3', 'yY', 2); // 11 + t('4', 'YxX', 2); // 100 + t('5', 'yXy', 2); // 101 BigNumber.config({ALPHABET: '0123456789*#'});