C Implementação de atof
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
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.
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 exemplod_nbr > FLT_MAX, ), uma escolha lógica éERANGEouEOVERFLOW. Se a conversão não puder ser concluída devido ao argumento malformado (por exemplo!isdigit(**str), ), a escolha lógica talvez sejaEINVAL.Dito isso, não endosso a configuração
errnona função da biblioteca. Uma tradição de longa data é definirerrnoapenas 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 comostrtoflida com isso:float strtof(const char *restrict nptr, char **restrict endptr);Aqui
nptré um in-only, eendptré 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.