1
1
/**
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`.
3
16
* @param {string } stringInBaseOne String in input base
4
17
* @param {string } baseOneCharacters Character set for the input base
5
18
* @param {string } baseTwoCharacters Character set for the output base
6
19
* @returns {string }
7
20
*/
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' ) ) {
10
23
throw new TypeError ( 'Only string arguments are allowed' )
11
24
}
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 ] ) {
14
30
if ( charactersInBase . length !== new Set ( charactersInBase ) . size ) {
15
31
throw new TypeError ( 'Duplicate characters in character set are not allowed' )
16
32
}
17
- } )
33
+ }
18
34
const reversedStringOneChars = [ ...stringInBaseOne ] . reverse ( )
19
35
const stringOneBase = baseOneCharacters . length
20
36
let value = 0
@@ -27,24 +43,57 @@ const convertArbitraryBase = (stringInBaseOne, baseOneCharacters, baseTwoCharact
27
43
value += ( digitNumber * placeValue )
28
44
placeValue *= stringOneBase
29
45
}
30
- let stringInBaseTwo = ''
46
+ const outputChars = [ ]
31
47
const stringTwoBase = baseTwoCharacters . length
32
48
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
36
52
}
37
- const baseTwoZero = baseTwoCharacters . charAt ( 0 )
38
- return stringInBaseTwo . replace ( new RegExp ( `^${ baseTwoZero } +` ) , '' )
53
+ return outputChars . reverse ( ) . join ( '' ) || baseTwoCharacters [ 0 ]
39
54
}
40
55
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
+ }
42
67
43
- // > convertArbitraryBase('98', '0123456789', '01234567')
44
- // '142'
68
+ const baseOneCharacters = [ ... baseOneCharacterString ]
69
+ const baseTwoCharacters = [ ... baseTwoCharacterString ]
45
70
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
+ }
48
98
49
- // > convertArbitraryBase('129', '0123456789', '01234567')
50
- // '201'
99
+ export { convertArbitraryBase , convertArbitraryBaseBigIntVersion }
0 commit comments