DanLevy.net

Тест: Можно ли доверять JavaScript?

Различаете parseInt и parseFloat?

Эта викторина проверит знания чисел в JavaScript: parseInt, parseFloat и BigInt. Разберём системы счисления, точность чисел с плавающей запятой и числовые ловушки языка.

Это всего лишь числа. Что тут сложного? 🤓

parseInt(" 123456.00")

parseInt игнорирует пробелы и парсит начальную последовательность цифр как целое число. Здесь он останавливается на десятичной точке, поэтому возвращается только 123456.

parseInt("123,456.00")

Как правило, parseInt прекращает разбор, когда встречает нечисловой символ. Здесь он останавливается на запятой, поэтому возвращается только 123.

0.1 + 0.2 === 0.3

Из‑за ошибок точности плавающих точек, 0.1 + 0.2 не равно точно 0.3. Из‑за того, как числа с плавающей запятой хранятся в памяти, результат равен 0.30000000000000004. Виноват стандарт IEEE 754 для работы с плавающей арифметикой — он не может точно представить некоторые числа. Это распространённая проблема во всех языках программирования. В конце концов вы столкнётесь с бесконечно повторяющейся десятичной дробью, и независимо от языка компьютер просто прекращает бесконечно генерировать цифры.

Некоторые языки, такие как Python и Java, имеют Decimal или BigDecimal для решения этой задачи, но в JavaScript их нет. Можно использовать библиотеки вроде big.js или decimal.js.

(Примечание: Некоторые языки предназначены для работы с дробями, мнимыми числами и т.п. на более высоком логическом уровне, сохраняя буквальные выражения. Но они всё равно сталкиваются с теми же проблемами точности плавающих точек на уровне аппаратуры.)

Number.MAX_VALUE * 2

Поскольку Number.MAX_VALUE — самое большое представимое обычное число в JavaScript, превышение его предела быстро приводит к переполнению — в результате получаются бессмысленные значения. Умножив его на 2, получаем Infinity.

Знаете, JavaScript иногда таков.

Что это может сделать?

5..toFixed(2)

.toFixed(2) возвращает строковое представление 5 с двумя знаками после запятой, поэтому результат — "5.00".

Двойная точка (5..toFixed(2)) — это «трюк», позволяющий обратиться к объектной модели числовых литералов.

parseInt("42") === parseFloat("42")

В JavaScript оба parseInt и parseFloat интерпретируют строку “42” как число 42. Поэтому сравнение parseInt("42") === parseFloat("42") дает true. parseInt прекращает разбор при первом символе, недопустимом для выбранного основания, тогда как parseFloat продолжает, пока не встретит символ, не являющийся частью числа с плавающей точкой. Однако в строке “42” нет десятичной точки или других недопустимых символов, поэтому обе функции возвращают одинаковое значение.

BigInt("42") === parseInt("42")

BigInt — это другой тип, отличающийся от number, поэтому parseInt("42") (обычное число) не строго равно BigInt("42"). Чтобы сравнить, нужно привести оба к одному типу: BigInt(parseInt("42")) === BigInt("42").

Что получится?

parseInt("0x2A") === parseInt("2a", 16)

Любая входная строка, начинающаяся с 0x, автоматически рассматривается как шестнадцатеричная (основание 16). Поэтому это эквивалентно передаче radix 16. Таким образом, parseInt("0x2A") то же самое, что parseInt("2a", 16). (Регистронезависимо.)

В чём тут дело?

parseInt('0xFF', 16)

parseInt с шестнадцатеричным основанием (16) преобразует "FF" в 255 в десятичной системе. Вы могли видеть это в CSS‑цветах RGB/Hex.

[24, 'One', 42].map(parseInt)

Второй аргумент parseInt (radix) совпадает с аргументом index, который передаёт метод массива. Это приводит к неожиданным результатам, так как parseInt("One", 1) возвращает NaN из‑за недопустимого ввода.

Первый элемент, 24, парсится как 24 в базе 0 (авто‑детект), поэтому остаётся 24. Второй элемент, 'One', парсится как NaN в базе 1. Третий элемент, 42, парсится в базе 2. В базе 2 строка '42' даёт NaN, так что результат [24, NaN, NaN].

Это распространённая ловушка при использовании parseInt с map. Если нужно превратить массив строк в числа, можно использовать .map(Number) для преобразования всей строки или явно указать основание при разборе числового префикса, например .map(x => parseInt(x, 10)).

[24, 'Twenty1', 0o42].map(Number)

Number преобразует значения в числовой тип строже, чем parseInt. Здесь 'Twenty1' превращается в NaN, а 0o42 распознаётся как восьмеричный литерал и преобразуется в 34.

Каков будет результат этого кода?

console.log(parseInt(null), Number(null))

parseInt приводит ввод к строке, поэтому null становится "null". Поскольку у "null" нет валидных десятичных символов (обычных цифр), он вернёт NaN.

Number(null) возвращает 0. потому что JS любит держать вас в напряжении. Почему? Ну, могу углубиться, если есть интерес.

Каков будет результат этого заклинания?

parseInt(null, 36)

Поскольку parseInt всегда приводит ввод к строке, null превращается в строку "null".

В системе счисления с основанием 36 (цифры 0–9 и латинские буквы a–z), строка "null" представляет 1112745.

Последовательные значения nulk, null и nulm соответственно 1112744, 1112745 и 1112746 в системе base 36.

Таблица сравнения

ФункцияparseIntparseFloatNumberBigInt
Игнорирует пробелы✅✅✅✅
.map(FN)❌☑️✅✅
Поддерживает аргумент radix✅❌❌❌
Двоичные/восьмеричные/шестнадцатеричные префиксы в строкахТолько 0x; префиксы 0b и 0o не распознаются автоматически❌✅✅
Недопустимые символы 42 oh no4242NaNSyntaxError

Как у вас получилось? 🧐

Нужен перерыв после такой дозы бинарных данных?
Пфф, помните: делайте перерыв после навыков!

Загляните в мой зал и разберите ещё несколько задач! 💪