C Implementación de atof

Aug 21 2020

Soy un principiante en C. Actualmente estoy implementando atof para construir un trazador de rayos, sin embargo, todavía estoy aprendiendo cómo escribir programas de manera eficiente.

asignación

Instrucciones

El programa toma un archivo de descripción de escena como argumento para generar objetos. Algunos de estos parámetros son flotantes. Ejemplo del archivo.

Estoy analizando el archivo. Como estoy restringido en términos de líneas permitidas por función y actualmente estoy aprendiendo cómo funcionan los punteros dobles, estoy usando un puntero de doble carácter. Ejemplo de una de esas funciones usando 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);
}


Código actual para revisar:

#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);
}

Los únicos ajustes al atof real que he hecho es tener un puntero de doble carácter como argumento y devolver -1 en caso de errores.

Cada entrada muy apreciada.

Respuestas

10 pacmaninbw Aug 21 2020 at 01:38

Portabilidad

No hay garantía de que este código use ASCII, por lo que sería mejor usarlo '0'en lugar de 48que sea algo así como un número mágico. El uso '0'lo hace más legible y más fácil de entender.

lc_atofNo maneja la terminación de cadena o el final de línea correctamente

Este código no maneja una cadena terminada en NULL o un carácter de fin de línea. La función isspace()regresa truepara el final de la línea, por lo que el código pasará directamente.

    while (isspace(**str))
        (*str)++;
    if (**str == '+' || **str == '-')
    {
        if (**str == '-')
            sign = -1.0;
        (*str)++;
    }
    if (!isdigit(**str))
    {
        errno = EIO;
        return (-1);
    }

Complejidad

Me doy cuenta de que no pidió que se revisara esto, pero la complejidad de cada ifdeclaración en la función de llamada de ejemplo es demasiado y me hizo cometer un error en mi revisión anterior:

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);
}

Yo reescribiría el código como:

#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);
}

lo que muestra fielmente la complejidad de la función.

10 vnp Aug 21 2020 at 06:19
  • Elegir EIOel informe de errores es muy dudoso. lc_atofno hace ninguna entrada o salida; ¿Por qué debería informar un error de IO? Si el tipo de devolución no puede representar el resultado (por ejemplo d_nbr > FLT_MAX, ), una opción lógica es ERANGEo EOVERFLOW. Si la conversión no puede completarse debido a un argumento mal formado (p. ej !isdigit(**str).), la opción lógica quizás sea EINVAL.

    Dicho esto, no apruebo la configuración errnoen la función de biblioteca. Una larga tradición es configurar errnosolo llamadas al sistema. Sé que esta tradición se viola cada vez más en estos días, pero aún así. Si tiene otros medios para informar errores, quédese con ellos.

  • No es aconsejable usar el parámetro inout ( stren su caso). Complica innecesariamente el código, tanto en el lado de la persona que llama como en el lado del destinatario. El receptor de la llamada se ve obligado a usar el direccionamiento indirecto adicional demasiadas veces y a preocuparse por los paréntesis (**str)++. A su vez, la persona que llama pierde la noción de dónde comenzó el análisis (por ejemplo, necesita registrar el número con formato incorrecto). Mira como strtofse maneja esto:

      float strtof(const char *restrict nptr, char **restrict endptr);
    

    Aquí nptrhay solo entrada y solo endptrsalida.

  • Me sorprende que haya decidido limitar la utilidad de la función manejando solo un dígito después del punto decimal. No es un gran esfuerzo manejarlos todos, y los beneficios son mucho mayores.

  • No es necesario poner entre paréntesis el valor devuelto. returnes un operador, no una función.