← Назад к вопросам

Валидация числа

1.8 Middle🔥 161 комментариев
#Теория тестирования

Условие

Напишите функцию, которая проверяет, является ли строка валидным числом (целым или с плавающей точкой).

Пример

Вход: 123 Выход: true

Вход: 12.5 Выход: true

Вход: 12a Выход: false

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI7 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Анализ задачи и подход к решению

Задача на валидацию строки как числа (целого или с плавающей точкой) классическая и часто встречается на собеседованиях. Она проверяет понимание конечных автоматов (Finite State Machine, FSM), умение работать с граничными случаями и писать чистый, тестируемый код.

Основная сложность — корректно обработать все возможные форматы числа без использования регулярных выражений (если это явно не разрешено) и встроенных функций преобразования строк в числа (например, parseInt, parseFloat, Number()), так как они могут некорректно обрабатывать некоторые случаи (например, parseInt("12.5") вернет 12, игнорируя .5).

Ключевые аспекты валидации

  • Целая часть: Одна или более цифр. Может начинаться со знака + или -.
  • Дробная часть (для чисел с плавающей точкой): Следует за точкой ., содержит одну или более цифр.
  • Порядок обработки: Строка должна быть обработана полностью, без лишних символов в конце.
  • Дополнительные правила (по умолчанию): Обычно не разрешаются ведущие нули, если это не просто 0, научная нотация (например, 1e5), пробелы в начале или конце, разделители тысяч (запятые). В условии задачи эти моменты часто уточняются.

Реализация на основе конечного автомата (FSM)

Наиболее надежный и понятный способ — реализовать детерминированный конечный автомат (DFA). Мы определяем состояния, в которых может находиться разбор строки, и правила перехода между ними в зависимости от текущего символа.

Определение состояний

  1. START: Начальное состояние.
  2. SIGN: Обнаружен знак (+ или -).
  3. INTEGER: Обнаружена хотя бы одна цифра целой части.
  4. DOT: Обнаружена точка, но еще нет цифр дробной части.
  5. FRACTION: Обнаружена точка и хотя бы одна цифра дробной части.
  6. INVALID: Обнаружен некорректный символ или последовательность.

Правила переходов

  • Из START:
    *   `+` или `-` -> **SIGN**
    *   цифра (`0-9`) -> **INTEGER**
    *   `.` -> **DOT**
    *   любой другой символ -> **INVALID**
  • Из SIGN:
    *   цифра (`0-9`) -> **INTEGER**
    *   `.` -> **DOT**
    *   любой другой символ -> **INVALID**
  • Из INTEGER:
    *   цифра (`0-9`) -> **INTEGER** (остаемся в том же состоянии)
    *   `.` -> **FRACTION** (переход к дробной части, т.к. целая часть уже есть)
    *   конец строки -> **Валидация успешна**
    *   любой другой символ -> **INVALID**
  • Из DOT:
    *   цифра (`0-9`) -> **FRACTION**
    *   любой другой символ или конец строки -> **INVALID** (число не может заканчиваться на точку)
  • Из FRACTION:
    *   цифра (`0-9`) -> **FRACTION** (остаемся в том же состоянии)
    *   конец строки -> **Валидация успешна**
    *   любой другой символ -> **INVALID**

Код реализации на JavaScript

function isValidNumber(str) {
    // Определяем функцию-предикат для проверки, является ли символ цифрой
    const isDigit = (ch) => ch >= '0' && ch <= '9';

    let state = 'START';

    for (let i = 0; i < str.length; i++) {
        const ch = str[i];

        switch (state) {
            case 'START':
                if (ch === '+' || ch === '-') {
                    state = 'SIGN';
                } else if (isDigit(ch)) {
                    state = 'INTEGER';
                } else if (ch === '.') {
                    state = 'DOT';
                } else {
                    return false;
                }
                break;

            case 'SIGN':
                if (isDigit(ch)) {
                    state = 'INTEGER';
                } else if (ch === '.') {
                    state = 'DOT';
                } else {
                    return false;
                }
                break;

            case 'INTEGER':
                if (isDigit(ch)) {
                    // Остаемся в INTEGER
                } else if (ch === '.') {
                    state = 'FRACTION';
                } else {
                    return false; // Недопустимый символ в середине числа
                }
                break;

            case 'DOT':
                if (isDigit(ch)) {
                    state = 'FRACTION';
                } else {
                    return false; // После точки должна быть цифра
                }
                break;

            case 'FRACTION':
                if (!isDigit(ch)) {
                    return false; // В дробной части допустимы только цифры
                }
                break;

            default:
                return false;
        }
    }

    // Проверяем, находимся ли мы в допустимом конечном состоянии
    // Допустимые конечные состояния: INTEGER (целое число) или FRACTION (дробное число)
    return state === 'INTEGER' || state === 'FRACTION';
}

// Тесты
console.log(isValidNumber("123"));    // true
console.log(isValidNumber("12.5"));   // true
console.log(isValidNumber("12a"));    // false
console.log(isValidNumber("-5.7"));   // true
console.log(isValidNumber("+3.14"));  // true
console.log(isValidNumber("."));      // false
console.log(isValidNumber("12."));    // false
console.log(isValidNumber(".5"));     // true
console.log(isValidNumber("+-5"));    // false
console.log(isValidNumber(""));       // false

Альтернативные подходы

  1. Регулярные выражения (Regex): Самое простое решение, если оно разрешено.
    function isValidNumberRegex(str) {
        // ^ - начало строки, [+-]? - необязательный знак,
        // \d+ - одна или более цифр (целая часть),
        // (\.\d+)? - необязательная дробная часть (точка и одна или более цифр),
        // $ - конец строки.
        return /^[+-]?\d+(\.\d+)?$/.test(str);
    }
    
    Однако на собеседовании часто просят реализовать алгоритм "вручную", чтобы оценить логическое мышление.

  1. Инкрементальный разбор (парсинг) без явного FSM: Можно использовать флаги (hasDigit, hasDot, hasSign), но это часто приводит к более сложной и запутанной логике с множеством условий.

Важные вопросы для уточнения у интервьюера

  • Разрешены ли пробелы в начале или конце строки? (Обычно нет, но если да — их нужно тримить).
  • Разрешена ли научная нотация (1e-5, 2E10)? Это значительно усложнит автомат.
  • Как обрабатывать ведущие нули (0012)? Обычно они недопустимы для целых чисел.
  • Разрешены ли числа, начинающиеся с точки (.5)? В нашем решении они разрешены.
  • Можно ли использовать встроенные функции или регулярные выражения? Как правило, нет.

Заключение

Реализация через конечный автомат — это профессиональный, масштабируемый и надежный подход. Он четко разделяет логику, легко модифицируется при изменении требований (например, добавление поддержки научной нотации) и демонстрирует глубокое понимание процесса разбора данных. Для QA Automation Engineer важно не только написать работающий код, но и понимать его логику, уметь составлять тестовые сценарии, покрывающие все состояния и переходы (например, таблица принятия решений или техника классов эквивалентности и граничных значений).