Avatar ·

Как проверить, является ли строка в JavaScript валидным числом?

Всем привет! Я новичок в JavaScript и пишу небольшое приложение для калькулятора. Пользователь вводит значения в текстовые поля, и мне нужно убедиться, что введённые данные — это числа, прежде чем выполнять математические операции. Проблема в том, что я не могу придумать надёжный способ проверки. Использовал `typeof` — но он показывает "string" для всего, что приходит из input. Пробовал `isNaN()` — но он странно себя ведёт: `isNaN("123")` возвращает `false` (хорошо), но `isNaN("12.3abc")` тоже возвращает `false`, хотя это явно не число! А ещё `isNaN("")` возвращает `false`, пустая строка считается числом — это вообще непонятно. Также пробовал `Number(" ")` — он возвращает 0, что тоже сбивает с толку. Ещё слышал про `parseFloat` и `parseInt`, но они обрезают строку и возвращают число, если строка начинается с цифр, например `parseFloat("123abc")` вернёт 123 — это тоже не то, что мне нужно. Мне нужно строгое соответствие: вся строка должна быть числом, без лишних символов. Вот пример моего кода, который не работает как надо: ```javascript function checkNumber(input) { if (isNaN(input)) { return "Это не число"; } return "Число"; } console.log(checkNumber("12.3abc")); // Ожидаю "не число", получаю "число" ``` Подскажите, пожалуйста, как правильно проверять строку на то, что она является валидным числом? Нужно учитывать: целые числа, дробные числа (с точкой), отрицательные числа, и строки с пробелами по краям (их можно обрезать). Заранее спасибо! <<>>

Надёжный способ: комбинация isFinite() и приведения типов

Лучший и наиболее надёжный способ проверить, является ли строка валидным числом в JavaScript — это использовать функцию isFinite() вместе с явным преобразованием строки в число. Этот подход решает все проблемы, с которыми вы столкнулись: isNaN("12.3abc") работает неправильно, пустая строка считается числом, а parseFloat обрезает хвост.

Вот правильное решение:

function isValidNumber(str) {
  // Обрезаем пробелы с краёв
  const trimmed = str.trim();
  
  // Проверяем, что строка не пуста после обрезки
  if (trimmed === '') return false;
  
  // Преобразуем в число и проверяем, является ли оно конечным числом
  return isFinite(Number(trimmed));
}

// Тесты
console.log(isValidNumber("123"));       // true
console.log(isValidNumber("-123"));      // true
console.log(isValidNumber("12.3"));      // true
console.log(isValidNumber("0.5"));       // true
console.log(isValidNumber("  42  "));    // true (пробелы обрезаются)
console.log(isValidNumber("12.3abc"));   // false
console.log(isValidNumber("abc"));       // false
console.log(isValidNumber(""));          // false
console.log(isValidNumber("   "));       // false
console.log(isValidNumber("Infinity"));  // false
console.log(isValidNumber("-Infinity")); // false
console.log(isValidNumber("NaN"));       // false

Почему это работает?

  • str.trim() — удаляет пробелы в начале и конце строки. Это важно, потому что пользователи часто случайно ставят лишние пробелы.
  • Number(trimmed) — преобразует строку в число. Если строка — валидное число, результатом будет число (например, 123). Если строка не является числом, результатом будет NaN.
  • isFinite() — проверяет, является ли значение конечным числом. Она возвращает false для:
    • NaN (не число)
    • Infinity и -Infinity (бесконечность)
    • undefined
    При этом isFinite() корректно обрабатывает строки, потому что JavaScript автоматически преобразует строку в число внутри этой функции.
  • Проверка на пустую строку — важна, потому что Number("") возвращает 0, а isFinite(0) возвращает true. Но пустая строка — это не число!

Альтернатива с регулярным выражением

Если вы предпочитаете более явный подход, можно использовать регулярное выражение для проверки формата числа:

function isValidNumberRegex(str) {
  const trimmed = str.trim();
  // Регулярное выражение для целых и дробных чисел (включая отрицательные)
  return /^-?\d+(\.\d+)?$/.test(trimmed);
}

console.log(isValidNumberRegex("123"));       // true
console.log(isValidNumberRegex("-123.45"));   // true
console.log(isValidNumberRegex("12.3abc"));   // false

Однако этот метод не обрабатывает научную нотацию (например, "1e5") и может быть менее производительным для больших объёмов данных. Поэтому первый вариант с isFinite() является более универсальным и рекомендуется к использованию в большинстве случаев.

Почему isNaN() подводит?

Функция isNaN() имеет особенность: она сначала преобразует аргумент в число. Поэтому isNaN("12.3abc") сначала пытается преобразовать строку в число, получает NaN и возвращает true — то есть "это не число". Но isNaN("") преобразует пустую строку в 0, а 0 — это число, поэтому возвращается false (строка считается числом). Это неинтуитивно и ненадёжно для валидации пользовательского ввода.

В современных проектах рекомендуется использовать Number.isNaN() (статический метод), который не выполняет принудительное преобразование типов, но для полной валидации строки его всё равно недостаточно — он не отсекает пустые строки и бесконечности.

<<>>

Использование Number.parseFloat() с дополнительной проверкой

Другой подход — использовать parseFloat() вместе с проверкой, что результат преобразования совпадает с исходной строкой после обрезки:

function isValidNumberParse(str) {
  const trimmed = str.trim();
  if (trimmed === '') return false;
  
  const num = parseFloat(trimmed);
  // Проверяем, что результат преобразования — конечное число
  // И что при обратном преобразовании в строку мы получаем то же самое
  return isFinite(num) && String(num) === trimmed;
}

// Тесты
console.log(isValidNumberParse("123"));       // true
console.log(isValidNumberParse("12.3"));       // true
console.log(isValidNumberParse("12.3abc"));   // false (parseFloat даст 12.3, но "12.3" !== "12.3abc")
console.log(isValidNumberParse("  42  "));    // false (пробелы не сохраняются)
console.log(isValidNumberParse("00123"));     // false (parseFloat вернёт 123, но "123" !== "00123")

Особенности этого подхода:

  • Он строже: отклоняет строки с ведущими нулями (например, "00123") и пробелами внутри числа.
  • Не поддерживает научную нотацию (например, "1e5" — parseFloat преобразует в 100000, но "100000" !== "1e5").
  • Подходит для случаев, когда нужно строгое соответствие формату ввода.

Для большинства задач валидации пользовательского ввода (например, поля для ввода цены, возраста, количества) достаточно первого решения с isFinite(). Этот вариант стоит использовать, если важна точность формата, например, при проверке номеров документов или кодов.