Почему преобразование JavaScript base-36 кажется неоднозначным

В настоящее время я пишу фрагмент JavaScript, который использует кодировку base 36.

Я столкнулся с этой проблемой:

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

Появляется, чтобы вернуть "welcomebacg".

Я проверил это в консоли разработчика Chrome и Node.js с тем же результатом.

Есть ли какое-то логическое объяснение такому результату?


person nph    schedule 01.08.2020    source источник
comment
Моя первая мысль заключается в том, что проанализированное значение превышает максимально возможное значение, которое может точно представлять number (2 ^ 53, но ваше значение находится в порядке 36 ^ 11). Попробуйте использовать более новый тип BigInt вместо number.   -  person Dai    schedule 01.08.2020
comment
@Дай Юп. Parseint дает 118479146750471970, что больше, чем Число. MAX_SAFE_INTEGER   -  person Calculuswhiz    schedule 01.08.2020


Ответы (2)


Результат parseInt("welcomeback",36) больше, чем Number.MAX_SAFE_INTEGER (253-1) и поэтому не может быть точно представлено. Возможный обходной путь — выполнить базовое преобразование с помощью BigInt вручную.

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));

person Unmitigated    schedule 01.08.2020

Тип данных number в JavaScript — это 64-битное число с плавающей запятой, и оно может безопасно представлять целые числа только до 2^53-1, см. Какое наибольшее целочисленное значение в JavaScript, к которому число может перейти без потери точности?

Результат parseInt("welcomeback",36) выше этого предела. Результатом будет ближайшее число, которое может быть представлено.

Число JS может безопасно содержать 10 цифр с основанием 36, поэтому эффективный способ преобразовать его в BigInt — разбить строку на фрагменты по 10 цифр и объединить их. В других ответах показана аналогичная техника с использованием reduce, здесь используется 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;
}

person Joni    schedule 01.08.2020
comment
Как я мог обойти это? - person nph; 01.08.2020
comment
@nph Может быть, разделить строку? Конкатенация проста в JS. - person Calculuswhiz; 01.08.2020
comment
Эмпирическое правило @nph: всякий раз, когда вы находите числа слишком большими, работайте с цифрами отдельно. - person Robo Robok; 01.08.2020
comment
@Calculuswhiz Как вы могли бы использовать это для преобразования всей строки? - person nph; 01.08.2020