C Реализация atof

Aug 21 2020

Я новичок в 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 в случае ошибок.

Мы ценим каждый вклад.

Ответы

10 pacmaninbw Aug 21 2020 at 01:38

Портативность

Нет никакой гарантии, что этот код будет использовать 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);
}

что верно показывает сложность функции.

10 vnp Aug 21 2020 at 06:19
  • Выбор 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это оператор, а не функция.