C Реализация atof
Я новичок в C. В настоящее время я использую atof для создания трассировщика лучей, но я все еще учусь, как эффективно писать программы.
Назначение
инструкции
Программа принимает файл описания сцены в качестве аргумента для создания объектов. Некоторые из этих параметров являются плавающими. Пример файла.

Разбираю через файл. Поскольку я ограничен в количестве строк, разрешенных для каждой функции, и в настоящее время изучаю, как работают двойные указатели, я использую указатель на двойной символ. Пример использования одной такой функции lc_atof
.
int a_parsing(char *str, t_pars *data)
{
if (*(str++) == 'A')
{
if (((data->a_ratio = lc_atof(&str)) >= 0.0) && data->a_ratio <= 1.0 && errno == 0)
//
if (((data->a_R = lc_atoi(&str)) >= 0) && data->a_R <= 255 && errno == 0)
if (*(str++) = ',' && ((data->a_G = lc_atoi(&str)) >= 0) && data->a_G <= 255 && errno == 0)
if (*(str++) = ',' && ((data->a_B = lc_atoi(&str)) >= 0) && data->a_B <= 255 && errno == 0)
return (skip_space(&str));
}
return (0);
}
Текущий код для проверки:
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <errno.h>
#include <float.h>
static float conversion(char **str)
{
double d_nbr;
double power;
d_nbr = 0.0;
power = 10.0;
while (isdigit(**str))
{
d_nbr = d_nbr * 10.0 + (**str - 48);
if (d_nbr > FLT_MAX)
{
errno = EIO;
return (-1);
}
(*str)++;
}
if (**str == '.')
{
(*str)++;
if (isdigit(**str))
{
d_nbr = d_nbr * 10.0 + (**str - 48);
if (d_nbr > FLT_MAX)
{
errno = EIO;
return (-1);
}
(*str)++;
return ((float)(d_nbr / power));
}
}
errno = EIO;
return (-1);
}
float lc_atof(char **str)
{
float n;
int sign;
n = 0.0;
sign = 1.0;
if (!str || !*str)
{
errno = EIO;
return (-1);
}
while (isspace(**str))
(*str)++;
if (**str == '+' || **str == '-')
{
if (**str == '-')
sign = -1.0;
(*str)++;
}
if (!isdigit(**str))
{
errno = EIO;
return (-1);
}
if ((n = conversion(str)) == 0 && errno != 0)
return (-1);
return (sign * n);
}
Единственное, что я сделал для фактического atof, - это указатель на двойной символ в качестве аргумента и возврат -1 в случае ошибок.
Мы ценим каждый вклад.
Ответы
Портативность
Нет никакой гарантии, что этот код будет использовать ASCII, поэтому было бы лучше использовать, '0'
а не 48
что-то вроде магического числа. Использование '0'
делает его более читаемым и понятным.
lc_atof
Не обрабатывает окончание строки или конец строки правильно
Этот код не обрабатывает строку, оканчивающуюся NULL, или символ конца строки. Функция isspace()
возвращается true
в конце строки, поэтому код проходит мимо нее.
while (isspace(**str))
(*str)++;
if (**str == '+' || **str == '-')
{
if (**str == '-')
sign = -1.0;
(*str)++;
}
if (!isdigit(**str))
{
errno = EIO;
return (-1);
}
Сложность
Я понимаю, что вы не просили, чтобы это было проверено, но сложность каждого if
оператора в примере функции вызова слишком велика и заставила меня сделать ошибку в моем обзоре ранее:
int a_parsing(char* str, t_pars* data)
{
if (*(str++) == 'A')
{
if (((data->a_ratio = lc_atof(&str)) >= 0.0) && data->a_ratio <= 1.0 && errno == 0)
//
if (((data->a_R = lc_atoi(&str)) >= 0) && data->a_R <= 255 && errno == 0)
if (*(str++) = ',' && ((data->a_G = lc_atoi(&str)) >= 0) && data->a_G <= 255 && errno == 0)
if (*(str++) = ',' && ((data->a_B = lc_atoi(&str)) >= 0) && data->a_B <= 255 && errno == 0)
return (skip_space(&str));
}
return (0);
}
Я бы переписал код как:
#define MAX_COLOR 0xFF
int a_parsing_prime(char* str, t_pars* data)
{
if (*(str++) == 'A')
{
data->a_ratio = lc_atof(&str);
if (!errno && data->a_R <= MAX_COLOR)
{
if (*(str++) = ',')
{
data->a_G = lc_atoi(&str);
if (!errno && data->a_G <= MAX_COLOR)
{
if (*(str++) = ',')
{
data->a_B = lc_atoi(&str);
if (!errno && data->a_B <= MAX_COLOR)
{
return (skip_space(&str));
}
}
}
}
}
}
return (0);
}
что верно показывает сложность функции.
Выбор
EIO
сообщения об ошибках очень сомнительный.lc_atof
не выполняет никаких операций ввода и вывода; почему он должен сообщать об ошибке ввода-вывода? Если возвращаемый тип не может представить результат (напримерd_nbr > FLT_MAX
), логическим выбором будетERANGE
илиEOVERFLOW
. Если преобразование не может быть завершено из-за неправильного аргумента (например!isdigit(**str)
), возможно, будет логичным выборомEINVAL
.Тем не менее, я не поддерживаю установку
errno
в библиотечной функции. Давняя традиция - устанавливатьerrno
только системные вызовы. Я знаю, что в наши дни эту традицию все больше и больше нарушают, но все же. Если у вас есть другие средства сообщения об ошибках, придерживайтесь их.Использование параметра inout (
str
в вашем случае) не рекомендуется. Это излишне усложняет код как на стороне вызывающего, так и на стороне вызываемого. Вызываемый объект вынужден слишком много раз использовать дополнительные косвенные адреса и беспокоиться о заключении в скобки(**str)++
. В свою очередь, вызывающий абонент теряет информацию о том, где начался синтаксический анализ (например, ему необходимо зарегистрировать искаженное число). Посмотрите, как сstrtof
этим справляется:float strtof(const char *restrict nptr, char **restrict endptr);
Вот
nptr
только вход иendptr
только выход.Я удивлен, что вы решили ограничить полезность функции, обрабатывая только одну цифру после десятичной точки. Разобраться со всеми из них не составляет большого труда, а пользы гораздо больше.
Возвращаемое значение не нужно заключать в круглые скобки.
return
это оператор, а не функция.