JavaScript Еще раз о parseInt, или почему parseInt от ’09’ равно 0

Казалось бы тема про parseInt в javascript уже довольна избита, но до сих многие из разработчиков, как опытных, так и не очень, с поразительным упорством наступают на одни и те же грабли. Как известно, глобальный метод parseInt() в javascript используется для перевода числа из его строкового представления в целочисленное число. Этот метод имеет два параметра — собственно само строковое представление числа и необязательный параметр, который указывает основание системы счисления, в которой производится преобразование. Вот о втором параметре и пойдет сегодня речь. Если внимательно посмотреть на него, взвесить все «за» и «против», и взглянуть на пример кода из реально существующего веб-сервиса, то станет понятным, что лучше всегда явно указывать этот самый необязательный второй параметр parseInt. Иначе в некоторых частных случаях функция с parseInt может работать неправильно. В качестве живого примера рассмотрим функцию, которая проверяет контрольное число у свидетельств пенсионного страхования.

Свидетельство пенсионного страхования

Мало кто знает, что в номере этого свидетельства две последние цифры — это контрольное число. Алгоритм его проверки довольно хорошо описан в Википедии в статье «Контрольное число» (раздел «Страховой номер индивидуального лицевого счета» — Россия).

В одном реально существующем веб-сервисе для проверки СНИЛС по этому алгоритму используется следующий метод:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
isValidSnils: function(snils) {
    var k = [9, 8, 7, 6, 5, 4, 3, 2, 1];
 
    snils = snils.toString().replace(/[\s\-]/g, '');
 
    if (/^([\d]{11})$/.test(snils)) {
        // проверка проводится только для номеров > 001-001-998
        if (parseInt(snils.substring(0, 9), 10) > 1001998) { 
            var sum = 0,
                controlNumber = parseInt(snils.substring(9, 11));
 
            for (var index = 0; index < 9; index++) {
                sum += k[index] * snils.charAt(index);
            }
 
            var n1 = sum % 101;
 
            n1 = (n1 > 99) ? n1 % 100 : n1;
            return n1 == controlNumber;
        } else {
            return true;
        }
    } else { // не СНИЛС
        return false;
    }
}

Казалось бы, что не так в этом методе? А дело как раз в том, что у parseInt() в строке 10 не указан второй параметр с системой счисления. И это приводит к незаметным на первый взгляд ошибкам в некоторых частных случаях. И эти частные случаи возникают, когда контрольное число в СНИЛС равно ’08’ или ’09’. Потому что в этом случае метод parseInt(’09’) вернет 0 вместо 9, и проверка в строке 19 вернет false вместо true, отметив СНИЛС как невалидный.

Почему же так происходит? В приведенной в самом начале статьи ссылке на сайт javascript.ru дана явная и четкая рекомендация: «Большинство реализаций интерпретируют строки, начинающиеся с 0 как восьмеричные. Указывайте основание в вызове. Это даст более предсказуемые результаты.»

Ну а так как 9 — это недопустимое число в восьмеричной системе счисления, то parseInt() с чистой совестью возвращает 0. То же самое будет в случае с ’08’. Цифры 8 и 9 не существуют в восьмеричной системе счисления, равно как 2 не существует в двоичной системе счисления. По этому поводу сразу вспоминается цитата Бендера в одной из серий «Футурамы»:

Бендер
  • (Бендер) (сквозь сон): 11010010001111… AAAA!!!!
  • (Фрай): Бендер, что такое?
  • (Бендер): Жуткий кошмар! Нули и единицы повсюду. Кажется, я видел двойку. Настоящую двойку.
  • (Фрай): Это просто сон, Бендер. Двоек не бывает.

Поэтому десятую строку из метода isValidSnils() нужно писать только так:

controlNumber = parseInt(snils.substring(9, 11), 10);

Вывод из всего написанного может быть только один. Забудьте, что второй параметр в методе parseInt() необязательный. ВСЕГДА явно указывайте основание системы счисления при использовании метода parseInt(). Это поможет избежать подобных глупых ошибок.