C Implementação de atof

Aug 21 2020

Sou iniciante em C. Atualmente, estou implementando atof para construir um raytracer, mas ainda estou aprendendo a escrever programas com eficiência.

Atribuição

Instruções

O programa usa um arquivo de descrição de cena como argumento para gerar objetos. Alguns desses parâmetros são flutuantes. Exemplo do arquivo.

Estou analisando o arquivo. Como estou restrito em termos de linhas permitidas por função e atualmente estou aprendendo como os ponteiros duplos funcionam, estou usando um ponteiro duplo. Exemplo de uma dessas funções 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 atual para revisão:

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

Os únicos ajustes no atof real que fiz são ter um ponteiro de caractere duplo como argumento e retornar -1 em caso de erros.

Cada entrada muito apreciada.

Respostas

10 pacmaninbw Aug 21 2020 at 01:38

Portabilidade

Não há garantia de que este código usará ASCII, portanto, seria melhor usá-lo '0'em vez 48de um número mágico. O uso '0'torna-o mais legível e fácil de entender.

lc_atofNão manipula a terminação da string ou o fim da linha corretamente

Este código não manipula uma string terminada em NULL ou um caractere de fim de linha. A função isspace()retorna truepara o fim da linha para que o código passe por ela.

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

Complexidade

Entendo que você não pediu para que isso fosse revisado, mas a complexidade de cada ifinstrução na função de chamada de exemplo é demais e me fez cometer um erro em minha revisão 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);
}

Eu reescreveria o 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);
}

o que mostra verdadeiramente a complexidade da função.

10 vnp Aug 21 2020 at 06:19
  • Escolher EIOrelatórios de erros é muito duvidoso. lc_atofnão faz nenhuma entrada ou saída; por que deveria relatar erro de IO? Se o tipo de retorno não puder representar o resultado (por exemplo d_nbr > FLT_MAX, ), uma escolha lógica é ERANGEou EOVERFLOW. Se a conversão não puder ser concluída devido ao argumento malformado (por exemplo !isdigit(**str), ), a escolha lógica talvez seja EINVAL.

    Dito isso, não endosso a configuração errnona função da biblioteca. Uma tradição de longa data é definir errnoapenas chamadas de sistema. Eu sei que esta tradição é cada vez mais violada nos dias de hoje, mas ainda assim. Se você tiver outros meios de relatar erros, use-os.

  • Usar o parâmetro inout ( strno seu caso) não é aconselhável. Isso complica desnecessariamente o código, tanto no lado do chamador quanto no lado do receptor. O receptor é forçado a usar indireção extra muitas vezes e a se preocupar em colocar parênteses (**str)++. Por sua vez, o chamador perde a noção de onde a análise começou (digamos, ele precisa registrar o número malformado). Veja como strtoflida com isso:

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

    Aqui nptré um in-only, e endptré out-only.

  • Estou surpreso que você tenha decidido limitar a utilidade da função manipulando apenas um dígito após o ponto decimal. Não é um grande esforço lidar com todos eles, e os benefícios são muito maiores.

  • Não há necessidade de colocar parênteses no valor de retorno. returné um operador, não uma função.