11/**
2- * Converts a string from one base to other
2+ * Divide two numbers and get the result of floor division and remainder
3+ * @param {number } dividend
4+ * @param {number } divisor
5+ * @returns {[result: number, remainder: number] }
6+ */
7+ const floorDiv = ( dividend , divisor ) => {
8+ const remainder = dividend % divisor
9+ const result = Math . floor ( dividend / divisor )
10+ 11+ return [ result , remainder ]
12+ }
13+ 14+ /**
15+ * Converts a string from one base to other. Loses accuracy above the value of `Number.MAX_SAFE_INTEGER`.
316* @param {string } stringInBaseOne String in input base
417* @param {string } baseOneCharacters Character set for the input base
518* @param {string } baseTwoCharacters Character set for the output base
619* @returns {string }
720*/
8- const convertArbitraryBase = ( stringInBaseOne , baseOneCharacters , baseTwoCharacters ) => {
9- if ( [ stringInBaseOne , baseOneCharacters , baseTwoCharacters ] . map ( arg => typeof arg ) . some ( type => type !== 'string' ) ) {
21+ const convertArbitraryBase = ( stringInBaseOne , baseOneCharacterString , baseTwoCharacterString ) => {
22+ if ( [ stringInBaseOne , baseOneCharacterString , baseTwoCharacterString ] . map ( arg => typeof arg ) . some ( type => type !== 'string' ) ) {
1023 throw new TypeError ( 'Only string arguments are allowed' )
1124 }
12- [ baseOneCharacters , baseTwoCharacters ] . forEach ( baseString => {
13- const charactersInBase = [ ...baseString ]
25+ 26+ const baseOneCharacters = [ ...baseOneCharacterString ]
27+ const baseTwoCharacters = [ ...baseTwoCharacterString ]
28+ 29+ for ( const charactersInBase of [ baseOneCharacters , baseTwoCharacters ] ) {
1430 if ( charactersInBase . length !== new Set ( charactersInBase ) . size ) {
1531 throw new TypeError ( 'Duplicate characters in character set are not allowed' )
1632 }
17- } )
33+ }
1834 const reversedStringOneChars = [ ...stringInBaseOne ] . reverse ( )
1935 const stringOneBase = baseOneCharacters . length
2036 let value = 0
@@ -27,24 +43,57 @@ const convertArbitraryBase = (stringInBaseOne, baseOneCharacters, baseTwoCharact
2743 value += ( digitNumber * placeValue )
2844 placeValue *= stringOneBase
2945 }
30- let stringInBaseTwo = ''
46+ const outputChars = [ ]
3147 const stringTwoBase = baseTwoCharacters . length
3248 while ( value > 0 ) {
33- const remainder = value % stringTwoBase
34- stringInBaseTwo = baseTwoCharacters . charAt ( remainder ) + stringInBaseTwo
35- value /= stringTwoBase
49+ const [ divisionResult , remainder ] = floorDiv ( value , stringTwoBase )
50+ outputChars . push ( baseTwoCharacters [ remainder ] )
51+ value = divisionResult
3652 }
37- const baseTwoZero = baseTwoCharacters . charAt ( 0 )
38- return stringInBaseTwo . replace ( new RegExp ( `^${ baseTwoZero } +` ) , '' )
53+ return outputChars . reverse ( ) . join ( '' ) || baseTwoCharacters [ 0 ]
3954}
4055
41- export { convertArbitraryBase }
56+ /**
57+ * Converts a arbitrary-length string from one base to other. Doesn't lose accuracy.
58+ * @param {string } stringInBaseOne String in input base
59+ * @param {string } baseOneCharacters Character set for the input base
60+ * @param {string } baseTwoCharacters Character set for the output base
61+ * @returns {string }
62+ */
63+ const convertArbitraryBaseBigIntVersion = ( stringInBaseOne , baseOneCharacterString , baseTwoCharacterString ) => {
64+ if ( [ stringInBaseOne , baseOneCharacterString , baseTwoCharacterString ] . map ( arg => typeof arg ) . some ( type => type !== 'string' ) ) {
65+ throw new TypeError ( 'Only string arguments are allowed' )
66+ }
4267
43- // > convertArbitraryBase('98', '0123456789', '01234567')
44- // '142'
68+ const baseOneCharacters = [ ... baseOneCharacterString ]
69+ const baseTwoCharacters = [ ... baseTwoCharacterString ]
4570
46- // > convertArbitraryBase('98', '0123456789', 'abcdefgh')
47- // 'bec'
71+ for ( const charactersInBase of [ baseOneCharacters , baseTwoCharacters ] ) {
72+ if ( charactersInBase . length !== new Set ( charactersInBase ) . size ) {
73+ throw new TypeError ( 'Duplicate characters in character set are not allowed' )
74+ }
75+ }
76+ const reversedStringOneChars = [ ...stringInBaseOne ] . reverse ( )
77+ const stringOneBase = BigInt ( baseOneCharacters . length )
78+ let value = 0n
79+ let placeValue = 1n
80+ for ( const digit of reversedStringOneChars ) {
81+ const digitNumber = BigInt ( baseOneCharacters . indexOf ( digit ) )
82+ if ( digitNumber === - 1n ) {
83+ throw new TypeError ( `Not a valid character: ${ digit } ` )
84+ }
85+ value += ( digitNumber * placeValue )
86+ placeValue *= stringOneBase
87+ }
88+ const outputChars = [ ]
89+ const stringTwoBase = BigInt ( baseTwoCharacters . length )
90+ while ( value > 0n ) {
91+ const divisionResult = value / stringTwoBase
92+ const remainder = value % stringTwoBase
93+ outputChars . push ( baseTwoCharacters [ remainder ] )
94+ value = divisionResult
95+ }
96+ return outputChars . reverse ( ) . join ( '' ) || baseTwoCharacters [ 0 ]
97+ }
4898
49- // > convertArbitraryBase('129', '0123456789', '01234567')
50- // '201'
99+ export { convertArbitraryBase , convertArbitraryBaseBigIntVersion }
0 commit comments