C Implementación de atof
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
Portabilidad
No hay garantía de que este código use ASCII, por lo que sería mejor usarlo '0'
en lugar de 48
que 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_atof
No 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 true
para 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 if
declaració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.
Elegir
EIO
el informe de errores es muy dudoso.lc_atof
no 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 ejemplod_nbr > FLT_MAX
, ), una opción lógica esERANGE
oEOVERFLOW
. Si la conversión no puede completarse debido a un argumento mal formado (p. ej!isdigit(**str)
.), la opción lógica quizás seaEINVAL
.Dicho esto, no apruebo la configuración
errno
en la función de biblioteca. Una larga tradición es configurarerrno
solo 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 (
str
en 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 comostrtof
se maneja esto:float strtof(const char *restrict nptr, char **restrict endptr);
Aquí
nptr
hay solo entrada y soloendptr
salida.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.
return
es un operador, no una función.