Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why JavaScript base-36 conversion appears to be ambiguous

I am currently writing a piece of JavaScript that uses base 36 encoding.

I came across this problem:

parseInt("welcomeback",36).toString(36)

Appears to return "welcomebacg".

I tested this in the Chrome developer's console and Node.js with the same result.

Is there any logical explanation for this result?

like image 651
nph Avatar asked Aug 01 '20 00:08

nph


2 Answers

The result of parseInt("welcomeback",36) is larger than Number.MAX_SAFE_INTEGER (253-1) and thus cannot be accurately represented. A possible workaround is to perform base conversion with BigInt manually.

const str = "welcomeback";
const base = 36;
const res = [...str].reduce((acc,curr)=>
   BigInt(parseInt(curr, base)) + BigInt(base) * acc, 0n);
console.log(res.toString());
console.log(res.toString(36));
like image 141
Unmitigated Avatar answered Nov 07 '22 13:11

Unmitigated


The number data type in JavaScript is a 64-bit floating point number, and it can safely represent integers only up to 2^53-1, see What is JavaScript's highest integer value that a number can go to without losing precision?

The result of parseInt("welcomeback",36) is above that limit. The result is going to be the closest number that can be represented.

A JS Number can safely hold 10 base-36 digits, so an efficient way to parse it into a BigInt is to split the string into chunks of 10 digits, and combine them. The other answers shows a similar technique using reduce, here's one using forEach:

function toBigInt36(str) {
    const multiplier = BigInt(Math.pow(36, 10));
    const chunks = str.match(/.{1,10}/g);
    let result = BigInt(0);
    chunks.forEach((chunk) => {
        result = result * multiplier + BigInt(parseInt(chunk, 36))
    });
    return result;
}

like image 2
Joni Avatar answered Nov 07 '22 12:11

Joni