implementación de atoi en C

10 minutos de lectura

avatar de usuario
Adán

no puedo entender lo siguiente atoi código de implementación, específicamente esta línea:

k = (k << 3) + (k << 1) + (*p) - '0';

Aquí está el código:

int my_atoi(char *p) {
    int k = 0;
    while (*p) {
        k = (k << 3) + (k << 1) + (*p) - '0';
        p++;
     }
     return k;
}

Alguien puede explicar me lo ?

Otra pregunta: ¿cuál debería ser el algoritmo de atof implementación?

avatar de usuario
karoly horvath

<< es un cambio de bits, (k<<3)+(k<<1) es k*10escrito por alguien que pensó que era más inteligente que un compilador (bueno, estaba equivocado…)

(*p) - '0' es restar el valor del caracter 0 del personaje señalado por pconvirtiendo efectivamente el carácter en un número.

Espero que puedas descifrar el resto… solo recuerda cómo funciona el sistema decimal.

Aquí hay una especificación para la función estándar. atoi. Perdón por no citar el estándar, pero esto funcionará igual de bien (de: http://www.cplusplus.com/reference/clibrary/cstdlib/atoi/ )

La función primero descarta tantos caracteres de espacio en blanco (como en
isspace) según sea necesario hasta que se encuentre el primer carácter que no sea un espacio en blanco. Luego, a partir de este carácter, toma un signo más o menos inicial opcional seguido de tantos dígitos de base 10 como sea posible y los interpreta como un valor numérico.

La cadena puede contener caracteres adicionales después de los que forman el número entero, que se ignoran y no tienen ningún efecto sobre el comportamiento de esta función.

Si la primera secuencia de caracteres que no son espacios en blanco en str no es un número entero válido, o si no existe tal secuencia porque
str está vacío o solo contiene caracteres de espacio en blanco, no se realiza ninguna conversión y se devuelve cero.

  • “él estaba equivocado” – Um, tal vez, tal vez no. Depende del compilador y de cuándo se escribió dicha implementación. Quizás esto fue escrito hace algún tiempo cuando la optimización de los compiladores no era tan buena. No hay razón para volver atrás y cambiarlo ahora. Los escritores de implementaciones de bibliotecas estándar tienen que lidiar con muchos (muchos) más casos de uso de los que solemos hacer.

    – Ed S.

    9 de octubre de 2012 a las 0:02


  • @Ed S.: quizás Con algo #ifdefs.. Dudo que encuentre esto en cualquier código de biblioteca estándar común..

    –Karoly Horvath

    9 oct 2012 a las 0:03

  • @aroth: “Mejorar el rendimiento ofuscando el código nunca es una buena idea” – Casi siempre hace que un fragmento de código sea más difícil de entender al optimizarlo, y de vez en cuando se vuelve bastante complicado (aunque en ese caso se justifica un comentario). A veces, las cosas realmente requieren optimizaciones de muy bajo nivel y, a menos que tenga esa rara clase de usuario que se preocupa más por la calidad del código que por el rendimiento, decir que “nunca es una buena idea” es simplemente una tontería. pienso Dispositivo de Duff quisiera tener una charla.

    – Ed S.

    9 oct 2012 a las 0:10


  • No estoy de acuerdo con los dos. La Ley de Moore terminó hace más de 5 años; Las CPU ya no son más rápidas, solo obtienen más núcleos, lo que generalmente no ayuda perceptiblemente al usuario final. Sin embargo, se necesitaría un compilador patológicamente malo para no elegir la mejor manera de realizar la multiplicación por una constante. Escribir la multiplicación por 10 como bitshifts no es una optimización; es una expresión del código en lenguaje ensamblador que el autor quería que el compilador generara en una máquina en particular, que no es lo que debería expresar C.

    – R.. GitHub DEJA DE AYUDAR A ICE

    9 de octubre de 2012 a las 4:21

  • @R.. – ¡Hurra por el desacuerdo! Aunque la Ley de Moore es sigue siendo fuerte. tienes razón en eso la mayoría los nuevos transistores van hacia la adición de núcleos, sin embargo, algunos van hacia mejoras arquitectónicas que aumentan el IPC, y las mejoras de proceso permiten velocidades de reloj más altas. Por lo tanto, el rendimiento de un solo subproceso sigue mejorando con cada iteración (alrededor de un 10-20 %). No por el 2x completo, pero lo suficiente como para ser notable y eclipsar las ganancias que podrían obtenerse al ofuscar el código aquí y allá.

    – aroth

    10 de octubre de 2012 a las 0:03


avatar de usuario
R.. GitHub DEJAR DE AYUDAR A ICE

k = (k << 3) + (k << 1);

medio

k = k * 2³ + k * 2¹ = k * 8 + k * 2 = k * 10

¿Eso ayuda?

los *p - '0' término suma el valor del siguiente dígito; esto funciona porque C requiere que los caracteres de los dígitos tengan valores consecutivos, de modo que '1' == '0' + 1, '2' == '0' + 2etc.

En cuanto a tu segunda pregunta (atof), esa debería ser su propia pregunta, y es el tema de una tesis, no algo simple de responder…

  • @Adam El algoritmo es simplemente “Para cada nuevo dígito, multiplique el valor de los dígitos anteriores por 10 y agregue el nuevo dígito”. Sin embargo, tenga en cuenta los problemas mencionados por Karoly Horvath en su respuesta, si la entrada contiene algo más que dígitos decimales, obtendrá basura; en particular, no maneja números negativos. Y si la entrada es demasiado larga, tendrá un desbordamiento y un comportamiento indefinido.

    –Daniel Fischer

    9 oct 2012 a las 0:19

  • Ahora es mucho mejor… ¿puedes mostrarme el código sin bit a bit? otro problema ¿cuál debería ser el algoritmo atof?

    – Adán

    9 oct 2012 a las 0:22


#include <stdio.h>
#include <errno.h>
#include <limits.h>

double atof(const char *string);

int debug=1;

int main(int argc, char **argv)
{
    char *str1="3.14159",*str2="3",*str3="0.707106",*str4="-5.2";
    double f1,f2,f3,f4;
    if (debug) printf("convert %s, %s, %s, %s\n",str1,str2,str3,str4);
    f1=atof(str1);
    f2=atof(str2);
    f3=atof(str3);
    f4=atof(str4);

    if (debug) printf("converted values=%f, %f, %f, %f\n",f1,f2,f3,f4);
    if (argc > 1)
    {
        printf("string %s is floating point %f\n",argv[1],atof(argv[1]));
    }
}

double atof(const char *string)
{
    double result=0.0;
    double multiplier=1;
    double divisor=1.0;
    int integer_portion=0;

    if (!string) return result;
    integer_portion=atoi(string);

    result = (double)integer_portion;
    if (debug) printf("so far %s looks like %f\n",string,result);

    /* capture whether string is negative, don't use "result" as it could be 0 */
    if (*string == '-')
    {
        result *= -1; /* won't care if it was 0 in integer portion */
        multiplier = -1;
    }

    while (*string && (*string != '.'))
    {
        string++;
    }
    if (debug) printf("fractional part=%s\n",string);

    // if we haven't hit end of string, go past the decimal point
    if (*string)
    {
        string++;
        if (debug) printf("first char after decimal=%c\n",*string);
    }

    while (*string)
    {
        if (*string < '0' || *string > '9') return result;
        divisor *= 10.0;
        result += (double)(*string - '0')/divisor;
        if (debug) printf("result so far=%f\n",result);
        string++;
    }
    return result*multiplier;
}

  • Esto no funciona para "1 .1" entre otros problemas.

    – chqrlie

    30 de abril de 2019 a las 6:54

avatar de usuario
Juan Ko

Curiosamente, la página del manual para atoi no indica la configuración de errno, por lo que si está hablando de cualquier número> (2 ^ 31) -1, no tiene suerte y de manera similar para números menores que -2 ^ 31 (suponiendo 32 -bit int). Obtendrá una respuesta, pero no será lo que desea. Aquí hay uno que podría tomar un rango de -((2^31)-1) a (2^31)-1, y devolver INT_MIN (-(2^31)) si hay un error. errno podría verificarse para ver si se desbordó.

#include <stdio.h>
#include <errno.h>  /* for errno */
#include <limits.h> /* for INT_MIN */
#include <string.h> /* for strerror */

extern int errno;

int debug=0;
int atoi(const char *c)
{
    int previous_result=0, result=0;
    int multiplier=1;

    if (debug) printf("converting %s to integer\n",c?c:"");
    if (c && *c == '-')
    {
        multiplier = -1;
        c++;
    }
    else
    {
        multiplier = 1;
    }
    if (debug) printf("multiplier = %d\n",multiplier);
    while (*c)
    {
        if (*c < '0' || *c > '9')
        {
            return result * multiplier;
        }
        result *= 10;
        if (result < previous_result)
        {
            if (debug) printf("number overflowed - return INT_MIN, errno=%d\n",errno);
            errno = EOVERFLOW;
            return(INT_MIN);
        }
        else
        {
            previous_result *= 10;
        }
        if (debug) printf("%c\n",*c);
        result += *c - '0';

        if (result < previous_result)
        {
            if (debug) printf("number overflowed - return MIN_INT\n");
            errno = EOVERFLOW;
            return(INT_MIN);
        }
        else
        {
            previous_result += *c - '0';
        }
        c++;
    }
    return(result * multiplier);
}

int main(int argc,char **argv)
{
    int result;
    printf("INT_MIN=%d will be output when number too high or too low, and errno set\n",INT_MIN);
    printf("string=%s, int=%d\n","563",atoi("563"));
    printf("string=%s, int=%d\n","-563",atoi("-563"));
    printf("string=%s, int=%d\n","-5a3",atoi("-5a3"));
    if (argc > 1)
    {
        result=atoi(argv[1]);
        printf("atoi(%s)=%d %s",argv[1],result,(result==INT_MIN)?", errno=":"",errno,strerror(errno));
        if (errno) printf("%d - %s\n",errno,strerror(errno));
        else printf("\n");
    }
    return(errno);
}

avatar de usuario
majid daryadel

Aquí está mi implementación (probada con éxito con casos que contienen y comienzan con letras, +, – y cero). Intenté hacer ingeniería inversa atoi función en Estudio visual. Si la cadena de entrada solo contiene caracteres numéricos, podría implementarse en un bucle. pero se complica porque te debes cuidar ,+ y letras

int atoi(char *s)
{    
    int c=1, a=0, sign, start, end, base=1;
//Determine if the number is negative or positive 
    if (s[0] == '-')
        sign = -1;
    else if (s[0] <= '9' && s[0] >= '0')
        sign = 1;
    else if (s[0] == '+')
        sign = 2;
//No further processing if it starts with a letter 
    else 
        return 0;
//Scanning the string to find the position of the last consecutive number
    while (s[c] != '\n' && s[c] <= '9' && s[c] >= '0')
        c++;
//Index of the last consecutive number from beginning
    start = c - 1;
//Based on sign, index of the 1st number is set
    if (sign==-1)       
        end = 1;
    else if (sign==1)
        end = 0;
//When it starts with +, it is actually positive but with a different index 
//for the 1st number
    else
    { 
        end = 1;
        sign = 1;
    }
//This the main loop of algorithm which generates the absolute value of the 
//number from consecutive numerical characters.  
    for (int i = start; i >=end ; i--)
    {
        a += (s[i]-'0') * base;
        base *= 10;
    }
//The correct sign of generated absolute value is applied
    return sign*a;
}

avatar de usuario
pez vela009

sobre el código de sugerencia atoi () desde aquí:

y basado en atoi(), mi implementación de atof():

[have same limitation of original code, doesn’t check length, etc]

double atof(const char* s)
{
  double value_h = 0;
  double value_l = 0;
  double sign = 1;

  if (*s == '+' || *s == '-')
  {
    if (*s == '-') sign = -1;
    ++s;
  }

  while (*s >= 0x30 && *s <= 0x39)
  {
    value_h *= 10;
    value_h += (double)(*s - 0x30);
    ++s;
  }

  // 0x2E == '.'
  if (*s == 0x2E)
  {
    double divider = 1;
    ++s;
    while (*s >= 0x30 && *s <= 0x39)
    {
      divider *= 10;
      value_l *= 10;
      value_l += (double)(*s - 0x30);
      ++s;
    }
    return (value_h + value_l/divider) * sign;
  }
  else
  {
    return value_h * sign;
  }
}

¿Ha sido útil esta solución?