Преобразование строки в число на языке Си

Как перевести строку в число си

В языке Си преобразование строки в число – задача, требующая внимания к деталям. Стандартная библиотека предоставляет функции atoi, atol, atoll и strtol, но их поведение различается по надежности и возможностям обработки ошибок. Например, atoi не проверяет переполнение и возвращает 0 при некорректном вводе, что делает её ненадежной для критичных приложений.

Функция strtol – более гибкий инструмент. Она позволяет указать основание системы счисления (от 2 до 36), обрабатывает переполнение и возвращает указатель на первый необработанный символ. Пример использования:

long num = strtol(str, &endptr, 10);

Если endptr указывает на начало строки, преобразование не удалось. Это позволяет отличать ошибки от корректных значений, равных нулю.

Для работы с числами с плавающей точкой используйте strtod. Она аналогична strtol, но поддерживает экспоненциальную запись и дробные части. Пример:

double d = strtod("3.14159", &endptr);

Обратите внимание: функции семейства strto* требуют явной проверки errno на ERANGE при переполнении.

Избегайте самописных реализаций без крайней необходимости. Стандартные функции оптимизированы и протестированы, а их поведение задокументировано. Если требуется высокая производительность, рассмотрите использование sscanf с проверкой количества успешно преобразованных элементов:

if (sscanf(str, "%ld", &num) != 1) { /* ошибка */ }

Основные функции для преобразования строк в числа в стандартной библиотеке

В стандартной библиотеке Си преобразование строк в числа реализовано через функции семейства strto* и более простые atoi, atol, atof. Первые предоставляют расширенный контроль над процессом, включая обработку ошибок и указание основания системы счисления, вторые – минималистичны, но лишены проверок.

strtol и strtoul преобразуют строку в знаковое и беззнаковое длинное целое соответственно. Функции принимают три аргумента: указатель на строку, указатель на указатель для хранения позиции остановки разбора и основание системы счисления (от 2 до 36). Пример: long num = strtol("123abc", &endptr, 10); – в num запишется 123, а endptr укажет на символ ‘a’.

strtod работает с числами с плавающей точкой, поддерживая экспоненциальную нотацию. Синтаксис аналогичен strtol, но возвращает double. Пример: double d = strtod("3.14e-2", NULL); вернёт 0.0314. Для проверки ошибок сравните errno с ERANGE – переполнение или потеря значимости.

atoi, atol и atof – упрощённые аналоги без обработки ошибок. Они игнорируют некорректные символы после числа, не сообщают о переполнении и не поддерживают основания системы счисления. Используйте их только для доверенных данных, например: int n = atoi("42");. При неудаче возвращают 0, что неотличимо от корректного нуля.

Для разбора чисел с учётом локали (например, десятичный разделитель в виде запятой) используйте strtod_l или strtol_l с явным указанием локали через locale_t. Это критично для интернационализированных приложений. Пример: double d = strtod_l("1,23", NULL, locale); в немецкой локали вернёт 1.23.

При работе с strto* всегда проверяйте endptr. Если он указывает на начало строки – разбор не удался. Если на конец – строка полностью преобразована. Промежуточные случаи сигнализируют о частичном разборе, например: strtol("123.45", &endptr, 10) остановится на точке, записав в endptr её адрес.

Для оптимизации производительности избегайте многократных вызовов strto* на одних данных. Кэшируйте результаты или используйте буферы. В критичных к скорости участках рассмотрите ручной парсинг с isdigit и sscanf, но только при гарантированном формате входных данных.

Различия между atoi, atol, atof и их ограничения

atoi, atol и atof – функции из стандартной библиотеки Си для преобразования строк в числа. Основное отличие – тип возвращаемого значения: atoi возвращает int, atollong, atofdouble. Все три функции игнорируют начальные пробельные символы и останавливают парсинг при встрече первого недопустимого символа, но не сообщают об ошибках.

Функции не проверяют переполнение. Например, atoi("99999999999999999999") вернёт неопределённое значение, так как число выходит за пределы INT_MAX (обычно 2 147 483 647). Аналогично atol не справится с числами, превышающими LONG_MAX (часто 9 223 372 036 854 775 807). atof может потерять точность при работе с очень большими или малыми числами из-за ограничений формата double.

  • atoi и atol не обрабатывают экспоненциальную нотацию (например, "1.23e5"), в отличие от atof.
  • Все три функции возвращают 0, если строка не содержит корректного числа, что делает невозможным отличить ошибку от корректного нуля.
  • Локализация не учитывается: десятичный разделитель всегда точка, даже если в системе установлена запятая.

Для безопасного преобразования рекомендуется использовать strtol, strtoul или strtod. Эти функции позволяют:

  1. Проверять переполнение через параметр endptr.
  2. Определять позицию остановки парсинга.
  3. Работать с разными системами счисления (например, восьмеричной или шестнадцатеричной).

Пример критической ошибки: atoi("123abc") вернёт 123, не сигнализируя о некорректном окончании строки. В отличие от strtol, который через endptr позволит обнаружить, что парсинг завершился на символе 'a'. Для финансовых или высоконагруженных систем использование atoi, atol или atof без дополнительных проверок недопустимо.

Использование strtol и strtoul для безопасного преобразования с проверкой ошибок

Функции strtol и strtoul из заголовочного файла <stdlib.h> преобразуют строки в числа с расширенными возможностями проверки ошибок. В отличие от atoi, они позволяют обнаружить переполнение, некорректные символы и частично обработанные строки. strtol работает с знаковыми целыми (long), а strtoul – с беззнаковыми (unsigned long). Обе функции принимают три параметра: строку для преобразования, указатель на конец обработанной части и основание системы счисления (от 2 до 36).

Основные ошибки, которые можно выявить с помощью этих функций:

  • Переполнение: если результат выходит за пределы типа long или unsigned long, функции возвращают LONG_MAX/LONG_MIN или ULONG_MAX, а errno устанавливается в ERANGE.
  • Некорректные символы: если строка начинается с недопустимых символов (например, букв при основании 10), функции возвращают 0, а указатель на конец указывает на первый символ.
  • Частичное преобразование: если строка содержит допустимые цифры, за которыми следуют недопустимые (например, "123abc"), указатель на конец позволяет определить границу корректной части.

Пример безопасного использования strtol:

#include <stdlib.h>
#include <errno.h>
#include <limits.h>
long safe_strtol(const char *str, int base) {
char *endptr;
errno = 0;
long val = strtol(str, &endptr, base);
if (endptr == str) {
// Нет корректных цифр
return 0;
}
if (*endptr != '\0') {
// Строка содержит недопустимые символы после числа
return 0;
}
if (errno == ERANGE) {
// Переполнение
return (val == LONG_MAX) ? LONG_MAX : LONG_MIN;
}
return val;
}

Для strtoul логика аналогична, но проверяется только ULONG_MAX.

При выборе между strtol и strtoul учитывайте диапазон значений. Если требуются отрицательные числа – используйте strtol. Для беззнаковых значений strtoul предпочтительнее, так как исключает ошибки интерпретации знака. Всегда проверяйте errno и указатель на конец строки, чтобы избежать неявных ошибок при преобразовании.

Обработка переполнения и некорректных данных при конвертации

При использовании функций atoi(), atol() или strtol() в Си переполнение целочисленного типа остаётся незамеченным, так как эти функции не возвращают код ошибки. Например, строка "99999999999999999999" при конвертации в int (обычно 32-битный) вызовет неопределённое поведение из-за превышения диапазона INT_MAX (2 147 483 647). Для безопасной обработки используйте strtol() с проверкой указателя endptr и границ типа: если *endptr != '\0', строка содержит некорректные символы, а если результат выходит за пределы LONG_MIN/LONG_MAX, фиксируйте переполнение.

Для 64-битных чисел (long long) применяйте strtoll() с аналогичными проверками. Пример корректной обработки: если errno == ERANGE после вызова, число выходит за допустимый диапазон. Не полагайтесь на возвращаемое значение функции – оно может быть усечено. Дополнительно проверяйте ведущие пробелы и знаки: strtol() игнорирует пробелы, но оставляет их в endptr, что может ввести в заблуждение при валидации.

Для пользовательского ввода всегда используйте буфер фиксированного размера и проверяйте длину строки до конвертации. Строка длиннее 20 символов (для long long) гарантированно вызовет переполнение. При работе с плавающей точкой (strtod()) учитывайте, что errno == ERANGE сигнализирует о слишком большом или малом значении, но не о некорректном формате – для этого проверяйте endptr. Избегайте atof(): она не предоставляет механизмов обработки ошибок.

Как правильно проверять успешность преобразования строки в число

В языке Си функции преобразования строк в числа, такие как atoi(), strtol() и sscanf(), возвращают разные типы данных и коды ошибок. atoi() не предоставляет механизма проверки ошибок – при неудаче возвращает 0, что совпадает с корректным преобразованием строки «0». Это делает её ненадёжной для валидации. Вместо неё используйте strtol(), которая позволяет контролировать ошибки через параметры endptr и errno. Пример: если *endptr != '\0' после вызова, строка содержит нечисловые символы.

Для strtol() критически важно проверять значение errno после вызова. Если функция завершилась с ошибкой переполнения (ERANGE), результат будет равен LONG_MAX или LONG_MIN, но errno останется установленной. Пример проверки: if (errno == ERANGE) { /* обработка ошибки */ }. Также учитывайте базу системы счисления (второй аргумент функции) – некорректное значение приведёт к неверному результату или ошибке.

sscanf() возвращает количество успешно преобразованных полей. Для проверки успешности сравните возвращаемое значение с ожидаемым числом спецификаторов. Например, if (sscanf(str, "%d", &num) != 1) { /* ошибка */ }. Однако sscanf() не обрабатывает переполнения и игнорирует пробелы в начале строки, что может привести к ложным срабатываниям. Для строгой валидации используйте комбинацию strtol() с проверкой endptr.

При работе с числами с плавающей точкой (strtod()) алгоритм проверки аналогичен strtol(), но с учётом специфики типа double. Проверяйте errno на ERANGE и анализируйте endptr. Для дробных чисел важно учитывать локаль: в некоторых системах разделителем является запятая, а не точка. Используйте setlocale(LC_NUMERIC, "C") для принудительной установки точки как разделителя, если это критично для вашего кода.

Работа с числами в разных системах счисления (десятичная, шестнадцатеричная, двоичная)

В Си преобразование строк в числа с разными основаниями реализуется через функции семейства strto*: strtol, strtoul, strtoll и strtoull. Ключевой параметр – основание системы счисления (base), передаваемое вторым аргументом. Для десятичных чисел используйте 10, для шестнадцатеричных – 16, для двоичных – 2. Функции автоматически обрабатывают префиксы: 0x для hex и 0b (нестандартный, но поддерживаемый некоторыми компиляторами) для binary.

Пример преобразования шестнадцатеричной строки в long: strtol("0x1A3F", NULL, 16) вернёт 6719. Если строка содержит недопустимые символы, функция останавливается на первом из них, а указатель на оставшуюся часть строки записывается во второй аргумент. Для проверки ошибок сравните этот указатель с началом строки: если они равны, преобразование не удалось.

Двоичные строки требуют явного указания основания 2, так как стандарт Си не распознаёт префикс 0b в strtol. Альтернатива – использовать sscanf с форматом "%i", который автоматически определяет систему по префиксу, но работает только для целых чисел и не поддерживает обработку ошибок так же гибко, как strtol.

Обратите внимание на переполнение: strtol при превышении диапазона возвращает LONG_MAX или LONG_MIN и устанавливает errno в ERANGE. Для беззнаковых чисел используйте strtoul, который возвращает ULONG_MAX. Всегда проверяйте errno после вызова, чтобы отличить корректный ноль от ошибки.

При работе с двоичными данными удобно использовать побитовые операции. Например, для извлечения младших 4 бит числа: value & 0xF. Для конвертации 4-битного значения в символ hex: "0123456789ABCDEF"[value & 0xF]. Это быстрее и компактнее, чем циклы или ветвления, особенно в критичных по производительности участках кода.

Если требуется парсить строки с плавающей точкой в разных системах, используйте strtod. Он поддерживает только десятичные числа, поэтому для hex-чисел с плавающей точкой (например, 0x1.ffffp+10) применяйте strtof или strtold с основанием 0, которое позволяет автоматически определять систему по префиксу. Однако такие форматы редко встречаются на практике и требуют явной поддержки со стороны компилятора.

Преобразование строки в число с плавающей точкой через strtod

strtod из стандартной библиотеки Си (<stdlib.h>) преобразует строку в число двойной точности (double). Функция принимает два аргумента: указатель на строку и указатель на указатель на символ, где будет сохранена позиция первого необработанного символа. Возвращает преобразованное значение или 0.0 при ошибке.

Синтаксис: double strtod(const char *nptr, char **endptr). Первый параметр – строка с числом, второй – необязательный указатель для отслеживания конца разбора. Если endptr не нужен, передайте NULL. Пример: double num = strtod("3.14159abc", NULL); вернёт 3.14159, игнорируя «abc».

Функция поддерживает экспоненциальную нотацию (например, "1.23e-4"0.000123), шестнадцатеричные числа с плавающей точкой ("0x1.999999999999ap-4"0.1) и локальные настройки (разделители дробной части зависят от LC_NUMERIC). Для корректной работы с локалями используйте setlocale(LC_NUMERIC, "") перед вызовом.

Обработка ошибок критична: strtod не выбрасывает исключений. Проверяйте errno на ERANGE при переполнении (HUGE_VAL или -HUGE_VAL) или потере значимости (0.0). Пример проверки: if (errno == ERANGE) { /* ошибка */ }. Также контролируйте endptr – если он указывает на начало строки, преобразование не удалось.

Для строк с пробелами strtod автоматически пропускает ведущие пробельные символы (определяются isspace()). Однако завершающие пробелы не игнорируются – они останутся в *endptr. Пример: strtod(" -123.45 ", &endptr) вернёт -123.45, а endptr укажет на первый пробел после числа.

При работе с пользовательским вводом всегда проверяйте endptr на равенство исходной строке или на наличие неожиданных символов. Например, если *endptr != '\0', строка содержит нечисловые данные. Для повышения надёжности комбинируйте с strspn или регулярными выражениями, чтобы предварительно валидировать формат.

Для преобразования в float или long double используйте strtof и strtold соответственно. Они работают аналогично, но возвращают значения меньшей точности. Пример: float f = strtof("1.23", NULL);. Не полагайтесь на неявное приведение типов – это может привести к потере данных.

Создание собственной функции для конвертации строки в число

Стандартные функции atoi(), strtol() и sscanf() решают задачу конвертации, но их поведение не всегда соответствует специфическим требованиям. Например, atoi() не обрабатывает переполнение и возвращает 0 при ошибке, что делает невозможным различие между некорректным вводом и нулевым значением. Собственная реализация позволяет контролировать обработку ошибок, поддерживать нестандартные форматы (например, числа с разделителями) или оптимизировать производительность для узких сценариев.

Базовый алгоритм конвертации строки в целое число включает три этапа: пропуск пробелов, определение знака и последовательное извлечение цифр. Для каждого символа после знака проверяется принадлежность к диапазону '0'–'9'. Если символ не является цифрой, функция должна либо завершиться с ошибкой, либо вернуть частично распознанное значение. Пример минимальной реализации:

int my_atoi(const char *str) {
int result = 0;
int sign = 1;
while (*str == ' ') str++;
if (*str == '-') { sign = -1; str++; }
while (*str >= '0' && *str <= '9') {
result = result * 10 + (*str - '0');
str++;
}
return sign * result;
}

Обработка переполнения критична для безопасности. Если результат умножения на 10 и сложения с новой цифрой превышает INT_MAX или становится меньше INT_MIN, функция должна возвращать соответствующее граничное значение. Для этого перед каждой операцией проверяется условие: result > (INT_MAX - digit) / 10. В случае переполнения возвращается INT_MAX или INT_MIN с учётом знака.

Поддержка чисел с плавающей точкой требует расширения алгоритма. После извлечения целой части проверяется наличие десятичного разделителя (обычно '.' или ','). Дробная часть обрабатывается аналогично целой, но с уменьшением веса каждой цифры в 10 раз на каждом шаге. Например, строка "3.14" преобразуется в 3 + 1*0.1 + 4*0.01. Для повышения точности рекомендуется использовать тип double и избегать накопления ошибок округления.

Локализация влияет на распознавание разделителей и знака экспоненты. В некоторых регионах десятичным разделителем служит запятая, а разделителем тысяч – точка. Для универсальной функции необходимо либо принимать параметры формата, либо использовать системные настройки через locale.h. Пример обработки экспоненциальной записи (например, "1.23e-4") требует дополнительного этапа: после основного числа проверяется наличие 'e' или 'E', затем извлекается порядок.

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

Расширение функциональности может включать поддержку шестнадцатеричных чисел (префикс 0x), двоичных (префикс 0b) или восьмеричных (префикс 0). Для этого перед основным циклом добавляется проверка префиксов и соответствующее изменение базы системы счисления. Например, при обнаружении 0x база устанавливается в 16, а символы 'a'–'f' и 'A'–'F' преобразуются в значения 10–15.

Типичные ошибки и подводные камни при преобразовании строк в числа

Первая и самая распространённая ошибка – игнорирование возвращаемого значения функций atoi(), atol() и atof(). Эти функции не сообщают об ошибках: при некорректном вводе (например, строке "123abc") они возвращают частично преобразованное число (123) или 0, если преобразование невозможно с самого начала. Для проверки корректности используйте strtol(), strtoul() или strtod(), которые позволяют отследить конец обработанной части строки через указатель endptr. Если *endptr != '\0', строка содержит недопустимые символы.

Переполнение – критическая проблема при работе с целочисленными типами. Функции atoi() и strtol() не проверяют границы целевого типа: например, строка "99999999999999999999" вызовет неопределённое поведение при попытке записать её в int. Всегда указывайте базу системы счисления (второй аргумент strtol()) и проверяйте errno на ERANGE. Для long и long long используйте LONG_MAX/LONG_MIN и LLONG_MAX/LLONG_MIN из <limits.h>.

Локализация – неочевидный фактор, влияющий на преобразование чисел с плавающей точкой. Функции atof() и strtod() зависят от текущей локали: в некоторых странах десятичным разделителем служит запятая ("3,14"), а не точка. Чтобы избежать неожиданностей, принудительно устанавливайте локаль setlocale(LC_NUMERIC, "C") перед вызовом или используйте strtod_l() с явным указанием локали. Не забывайте, что errno также может устанавливаться в ERANGE при переполнении или потере точности.

Ссылка на основную публикацию