C Implementierung von atof

Aug 21 2020

Ich bin ein Anfänger in C. Ich implementiere derzeit atof, um einen Raytracer zu bauen, aber ich lerne immer noch, wie man effizient Programme schreibt.

Auftrag

Anweisungen

Das Programm nimmt eine Szenenbeschreibungsdatei als Argument, um Objekte zu erzeugen. Einige dieser Parameter sind float. Beispiel der Datei.

Ich durchsuche die Datei. Da ich in Bezug auf die zulässigen Zeilen pro Funktion eingeschränkt bin und ich gerade lerne, wie Doppelzeiger funktionieren, verwende ich einen Doppelzeiger. Beispiel für eine solche Funktion mit 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);
}


Aktueller zu überprüfender Code:

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

Die einzigen Änderungen an der eigentlichen Atof, die ich vorgenommen habe, sind ein doppelter Zeichenzeiger als Argument und die Rückgabe von -1 im Falle von Fehlern.

Jeder Input wird sehr geschätzt.

Antworten

10 pacmaninbw Aug 21 2020 at 01:38

Portabilität

Es gibt keine Garantie dafür, dass dieser Code ASCII verwendet, daher wäre es besser, ihn zu verwenden '0', als 48dass es sich um eine Art magische Zahl handelt. Die Verwendung '0'macht es lesbarer und leichter zu verstehen.

lc_atofBehandelt Zeichenfolgenterminierung oder Zeilenende nicht korrekt

Dieser Code verarbeitet keine NULL-terminierte Zeichenfolge oder ein Zeilenendezeichen. Die Funktion isspace()kehrt truefür das Zeilenende zurück, sodass der Code direkt daran vorbeigeht.

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

Komplexität

Mir ist klar, dass Sie nicht darum gebeten haben, dass dies überprüft wird, aber die Komplexität jeder ifAnweisung in der Beispielaufruffunktion ist zu groß und hat dazu geführt, dass ich zuvor einen Fehler in meiner Überprüfung gemacht habe:

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

Ich würde den Code umschreiben als:

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

was wirklich die Komplexität der Funktion zeigt.

10 vnp Aug 21 2020 at 06:19
  • Die Wahl EIOfür die Fehlerberichterstattung ist sehr zweifelhaft. lc_atofmacht keine Eingabe oder Ausgabe; Warum sollte es einen IO-Fehler melden? Wenn der Rückgabetyp das Ergebnis nicht darstellen kann (z. B. d_nbr > FLT_MAX), ist eine logische Wahl ERANGEoder EOVERFLOW. Wenn die Konvertierung aufgrund des falsch formatierten Arguments (z. B. !isdigit(**str)) nicht abgeschlossen werden kann, wäre die logische Wahl vielleicht EINVAL.

    Allerdings unterstütze ich die Einstellung errnoin der Bibliotheksfunktion nicht. Es ist eine lange Tradition, errnonur Systemaufrufe einzugeben. Ich weiß, dass diese Tradition heutzutage immer mehr verletzt wird, aber trotzdem. Wenn Sie andere Möglichkeiten zur Fehlerberichterstattung haben, bleiben Sie dabei.

  • Die Verwendung des inout-Parameters ( strin Ihrem Fall) ist nicht ratsam. Es verkompliziert den Code unnötigerweise sowohl auf der Seite des Anrufers als auch auf der Seite des Angerufenen. Der Aufgerufene ist gezwungen, zu oft eine zusätzliche Indirektion zu verwenden und sich Gedanken über Klammern zu machen (**str)++. Im Gegenzug verliert der Anrufer den Überblick darüber, wo die Analyse begonnen hat (z. B. muss er die falsch formatierte Nummer protokollieren). Schau, wie strtofdas geht:

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

    Hier nptrist ein Nur-In und ein Nur endptr-Out.

  • Ich bin überrascht, dass Sie sich entschieden haben, den Nutzen der Funktion einzuschränken, indem Sie nur eine Ziffer nach dem Dezimalpunkt behandeln. Es ist kein großer Aufwand, alle zu handhaben, und der Nutzen ist viel größer.

  • Der Rückgabewert muss nicht in Klammern gesetzt werden. returnist ein Operator, keine Funktion.