The C Programming Language, 2nd Edition, by Kernighan and Ritchie
Exercise 4.02 on page 73
Extend atof
to handle scientific notation of the form 123.45e-6
where a floating-point number may be followed by e or E and an optionally signed exponent.
Solution by Dann Corbit
/* ** Written by Dann Corbit as K&R 2, Exercise 4-2 (Page 73). ** Keep in mind that this is *JUST* a student exercise, and is ** light years away from being robust. ** ** Actually, it's kind of embarassing, but I'm too lazy to fix it. ** ** Caveat Emptor, not my fault if demons fly out of your nose, ** and all of that. */ #include <ctype.h> #include <limits.h> #include <float.h> #include <signal.h> #include <stdio.h> int my_atof(char *string, double *pnumber) { /* Convert char string to double data type. */ double retval; double one_tenth = 0.1; double ten = 10.0; double zero = 0.0; int found_digits = 0; int is_negative = 0; char *num; /* Check pointers. */ if (pnumber == 0) { return 0; } if (string == 0) { *pnumber = zero; return 0; } retval = zero; num = string; /* Advance past white space. */ while (isspace(*num)) num++; /* Check for sign. */ if (*num == '+') num++; else if (*num == '-') { is_negative = 1; num++; } /* Calculate the integer part. */ while (isdigit(*num)) { found_digits = 1; retval *= ten; retval += *num - '0'; num++; } /* Calculate the fractional part. */ if (*num == '.') { double scale = one_tenth; num++; while (isdigit(*num)) { found_digits = 1; retval += scale * (*num - '0'); num++; scale *= one_tenth; } } /* If this is not a number, return error condition. */ if (!found_digits) { *pnumber = zero; return 0; } /* If all digits of integer & fractional part are 0, return 0.0 */ if (retval == zero) { *pnumber = zero; return 1; /* Not an error condition, and no need to * continue. */ } /* Process the exponent (if any) */ if ((*num == 'e') || (*num == 'E')) { int neg_exponent = 0; int get_out = 0; long index; long exponent = 0; double getting_too_big = DBL_MAX * one_tenth; double getting_too_small = DBL_MIN * ten; num++; if (*num == '+') num++; else if (*num == '-') { num++; neg_exponent = 1; } /* What if the exponent is empty? Return the current result. */ if (!isdigit(*num)) { if (is_negative) retval = -retval; *pnumber = retval; return (1); } /* Convert char exponent to number <= 2 billion. */ while (isdigit(*num) && (exponent < LONG_MAX / 10)) { exponent *= 10; exponent += *num - '0'; num++; } /* Compensate for the exponent. */ if (neg_exponent) { for (index = 1; index <= exponent && !get_out; index++) if (retval < getting_too_small) { get_out = 1; retval = DBL_MIN; } else retval *= one_tenth; } else for (index = 1; index <= exponent && !get_out; index++) { if (retval > getting_too_big) { get_out = 1; retval = DBL_MAX; } else retval *= ten; } } if (is_negative) retval = -retval; *pnumber = retval; return (1); } /* ** Lame and evil wrapper function to give the exercise the requested ** interface. Dann Corbit will plead innocent to the end. ** It's very existence means that the code is not conforming. ** Pretend you are a C library implementer, OK? But you would fix ** all those bleeding gaps, I am sure. */ double atof(char *s) { double d = 0.0; if (!my_atof(s, &d)) { #ifdef DEBUG fputs("Error converting string in [sic] atof()", stderr); #endif raise(SIGFPE); } return d; } #ifdef UNIT_TEST char *strings[] = { "1.0e43", "999.999", "123.456e-9", "-1.2e-3", "1.2e-3", "-1.2E3", "-1.2e03", "cat", "", 0 }; int main(void) { int i = 0; for (; *strings[i]; i++) printf("atof(%s) = %g\n", strings[i], atof(strings[i])); return 0; } #endif
Solution by Pilcrow
I have replaced my previous effort.. This one contains a basic validity test. All input is via the keyboard...
#include <stdio.h> #include <ctype.h> #include "pilcrow.h" /* for getline */ #define MAXLINE 1024 #define PREFIX 1 #define NUMBER 2 #define EXPONENT 3 #define EXP_DIG 4 int valid_float(char s[]) { /* a valid float may be preceded by whitespace (\s, \t, \n, etc) */ /* leading optional + or - */ /* decimal digits and one optional . */ /* optional exponent (e/E, optional + or -, 1 to 3 decimal digits) */ /* I could do this in one line with a regex! */ int i, state, point, expct, ptct; state = PREFIX; point = 0; expct = 0; ptct = 0; for(i=0; s[i] != '\n' && s[i] != '\0'; i++) { if (isspace(s[i])) { if(state == PREFIX) continue; return 0; /* embedded space */ } if (isdigit(s[i])) { if(state == PREFIX || state == NUMBER) { state = NUMBER; continue; } if(state == EXPONENT || state == EXP_DIG) { if(++expct > 3) { return 0; /* exp may not have more than 3 digits */ } state = EXP_DIG; continue; } } switch(s[i]) { case '.': if(state == PREFIX || state == NUMBER) { if(++ptct > 1) { return 0; /* only one decimal pt */ } state = NUMBER; continue; } case 'e': case 'E': if(state == NUMBER) { state = EXPONENT; continue; } return 0; /* misplaced 'e' */ case '-': case '+': if(state == PREFIX) { state = NUMBER; continue; } if(state == EXPONENT) { state = EXP_DIG; continue; } return 0; /* misplaced sign */ default: return 0; /* illegal character */ } } return 1; /* everything ok */ } double atof(char s[]) { double val,power; int i,sign,sign2,exponent; for(i=0; isspace(s[i]); i++); sign = (s[i] == '-') ? -1 : 1; if(s[i] == '+' || s[i] == '-') i++; for(val = 0.0; isdigit(s[i]); i++) val = 10.0 * val +(s[i] - '0'); if(s[i] == '.') i++; for(power = 1.0; isdigit(s[i]); i++) { val = 10.0 * val + (s[i] - '0'); power *= 10.0; } if(s[i] == 'e' || s[i] == 'E') { i++; sign2 = (s[i] == '-') ? -1 : 1; if(s[i] == '+' || s[i] == '-') i++; for(exponent = 0; isdigit(s[i]); i++) exponent = 10 * exponent + (s[i] - '0'); if(sign2 == 1) for(i = exponent; i > 0; --i, val *=10.0); if(sign2 == -1) for(i = exponent; i > 0; --i, val /=10.0); } return sign * val / power; } int main(void) { char inp[MAXLINE+1]; while(getline(inp, MAXLINE-1) > 1) { /* terminates on empty line */ if(!valid_float(inp)) { printf("invalid input\n"); }else printf("% 9.9g\n", atof(inp)); } return 0; }
Solution by blob84
/* Exercise 4-2. Extend atof to handle scientific notation of the form 123.45e-6 where a floating-point number may be followed by e or E and an optionally signed exponent. */ #include <stdio.h> #include <ctype.h> double atof(char s[]); int main(void) { printf("%f\n", atof("123.45e-6")); } double atof(char s[]) { double val, power, base, p; int i, sign, exp; for (i = 0; isspace(s[i]); i++) ; sign = (s[i] == '-') ? -1 : 1; if (s[i] == '-' || s[i] == '+') ++i; for (val = 0.0; isdigit(s[i]); i++) val = 10.0 * val + (s[i] - '0'); if (s[i] == '.') i++; for (power = 1.0; isdigit(s[i]); i++) { val = 10.0 * val + (s[i] - '0'); power *= 10.0; } if (s[i] == 'e' || s[i] == 'E') i++; else return sign * val/power; base = (s[i] == '-') ? 0.1 : 10.0; /* 10^(-n) = 1/10^n = (1/10)^n = (0.1)^n */ if (s[i] == '+' || s[i] == '-') i++; for (exp = 0; isdigit(s[i]); i++) exp = 10 * exp + (s[i] - '0'); for (p = 1; exp > 0; --exp) p = p * base; return (sign * (val/power)) * p; }
Solution by Adam89
My solution is a small addition to the original "atof" function from K&R page 71. It is recursive and will work with positive or negative exponents, and will work if there is whitespace between the base number, the letter 'e' and the exponent. No validity checks, so it does assume the input is of a valid format e.g. only 1 decimal point in the exponent.
/************************************* myatof: return double from string input. Can also account for scientific input including negative and/or non-integer exponent, and white space between base, e, and exponent. e.g. "12.345 e -5.337" = 0.00005681867 *************************************/ #include<stdio.h> #include<stdlib.h> #include<ctype.h> #include<math.h> double myatof(char s[]); int main(void) { char num[] = "1288644.345 e -5.337"; printf("%.12f\n",myatof(num)); return 0; } double myatof(char s[]) { double val, power, exponent; int i, sign; for (i = 0; isspace(s[i]); i++) /*skip white space*/ ; sign = ( s [i] == '-' ) ? -1 : 1 ; if (s[i] == '+' || s[i] == '-') i++; for (val= 0.0; isdigit(s[i]); i++) val= 10.0 *val + (s[i] - '0'); if (s[i] == '.') i++; for (power= 1.0; isdigit(s[i]); i++) { val= 10.0 *val+ (s[i] - '0'); power *= 10.0; } for ( ; isspace(s[i]); i++) /*skip any further white space up to an e or E*/ ; if ((s[i] != '\0') && (tolower(s[i++]) == 'e')) /*** See 1 below ***/ exponent = myatof(&s[i]); /*** See 2 below ***/ else exponent = 0.0; return (sign * val / power) * pow(10.0,exponent); /* 1: Just in case we arrive at '\0' and , by some coincidence, the next char in memory happens to be 'e', we should stop or there would be problems i.e. we should only get the exponent if we are still inside s.*/ /* 2: Note the recursion here; for an original argument of "52.553 e -8.35", this line will pass the string " -8.35" and return -8.35 as double. If the exponent is omitted and a string of whitespace/an empty string is passed here, then 0.0 is returned.*/ }
Solution by menonsahab
/* I wrote up a recursive solution. Turns out Adam beat me to it. But I've avoided using the pow() function to keep this a category 0 solution. */ #include <stdio.h> double atof(char *s) { double res = 0.0, exponent = 0.0; int i = 0, c, sign = 1, flag = 0, k = 0; if(s[0] == '-') sign = -1; while( (c = s[i++]) != '\0') { if(c == 'e' || c =='E') { exponent = atof(&s[i]); break; } if(c == '.') flag = 1; else if(c >= '0' && c <= '9' ) { if(flag == 1) ++k; res = (c - '0') + (res * 10); } } exponent -= k; if(exponent < 0) while(exponent++) res /= 10.0; else if(exponent > 0) while(exponent--) res *= 10.0; return sign * res; } int main() { char s1[] = "87.549e2", s2[] = "-982.47e-3", s3[] = "-54e", s4[] = "-.64e4", s5[] = "+.87e+ 6"; printf("s1 = %10s <-> %15lf\n", s1, atof(s1)); printf("s2 = %10s <-> %15lf\n", s2, atof(s2)); printf("s3 = %10s <-> %15lf\n", s3, atof(s3)); printf("s4 = %10s <-> %15lf\n", s4, atof(s4)); printf("s5 = %10s <-> %15lf\n", s5, atof(s5)); return 0; }
Solution by Luke Panayi (cat 0)
My cat0 solution, using atoi instead of atof to avoid using recrusion and keep it cat0.
/* Extend atof to handle scientific notation of the form 123.45e-6 where a floating-point number may be followed by e or E and an optionally signed exponent. */ #include <stdio.h> #include <ctype.h> #include <math.h> #define BUFFER 1000 int atoi(char s[]) { int i, n, sign; for (i=0; isspace(s[i]); ++i); sign = (s[i] == '-') ? -1 : 1; if (s[i] == '+' || s[i] == '-') ++i; for (n=0; isdigit(s[i]); ++i) n = 10 * n + (s[i] - '0'); return sign * n; } double atof(char s[]) { double val, power; int i, sign; for (i=0; isspace(s[i]); i++); sign = (s[i] == '-') ? -1 : 1; if (s[i] == '+' || s[i] == '-') ++i; for (val = 0.0; isdigit(s[i]); ++i) val = 10.0 * val + (s[i] - '0'); if (s[i] == '.') ++i; for (power = 1.0; isdigit(s[i]); ++i) { val = 10.0 * val + (s[i] - '0'); power *= 10.0; } if (s[i] == 'e' || s[i] == 'E' ) { ++i; char t[BUFFER]; int j, n; for (j=0; s[i] != '\0'; ++j, ++i) { t[j] = s[i]; } t[j] = '\0'; n = atoi(t); //wanted to use recurrsion here with atof but we haven't met that yet so keeping it category 0. Works as the expodent is always expected to be an int anyway. power *= pow(10,-n); //flip the sign of n as power is dividing the final value } return sign * val / power; } int main() { char s[] = "-123.45e-6"; printf("%f\n", atof(s)); return 0; }
Solution by Miguel Degrossoli
/* Exercise 4-2. Extend atof to handle scientific notation of the form * 123.45e-6 * where a floating-point number may be followed by e or E and an optionally * signed exponent. */ #include <ctype.h> #include <stdio.h> #define MAXLINE 100 double atof(char s[]); int mygetline(char s[], int maxl); /* converts strings to double */ int main() { char line[MAXLINE]; while (mygetline(line, MAXLINE) > 0) printf("\t%f\n", atof(line)); return 0; } /* atof: convert string s to double */ double atof(char s[]) { double val, power; int i, sign, esign, exp; for (i = 0; isspace(s[i]); i++) /* skip white space */ ; sign = (s[i] == '-') ? -1 : 1; if (s[i] == '+' || s[i] == '-') i++; for (val = 0.0; isdigit(s[i]); i++) val = 10.0 * val + (s[i] - '0'); power = 1.0; if (s[i] == '.') { i++; for (; isdigit(s[i]); i++) { val = 10.0 * val + (s[i] - '0'); power *= 10; } } if (s[i] == 'e' || s[i] == 'E') i++; esign = (s[i] == '-') ? -1 : 1; if (s[i] == '+' || s[i] == '-') i++; for (exp = 0; isdigit(s[i]); i++) exp = 10 * exp + s[i] - '0'; for (i = 0; i < exp; i++) power *= (esign == -1) ? 10.0 : 0.1; return sign * val / power; } /* mygetline: reads a line of up to maxl chars and stores it into s. */ int mygetline(char s[], int maxl) { unsigned char c; int i = 0; while (i <= maxl && (c = getchar()) != EOF && c != '\n') s[i++] = c; s[i] = '\0'; return i; }
Samples:
miguel@Miguel-Notebook:~/Desenvolvimento/C$ ./exercise_4-2 123.45e-6 0.000123 123.45e6 123450000.000000 -123.45e-6 -0.000123 -123.45e6 -123450000.000000
Solution by anonymous
My approach is similar to most, except I created a function to handle the exponent portion. This is only because atof is a function in math.h so I couldn't use the pow function like I wanted too. My goal was to follow the coding style of the book as much as possible.
#include <stdio.h> #include <ctype.h> /* Extend atof to handle scientific notation of the form 123.45e-6 where a floating-point number may be followed by e or E and an optionally signed exponent */ #define MAXLINE 100 double atof(char s[]); double powd(double base, double p); int main() { printf("%f\n", atof("123.45e-6")); printf("%f\n", atof("+123.45e-6")); printf("%f\n", atof("-123.45e-6")); printf("%f\n", atof("123.45e6")); printf("%f\n", atof("+123.45e+6")); printf("%f\n", atof("-123.45e6")); return 0; } // convert strings to double double atof(char s[]) { double val, power; int i, sign, signE, exp; for (i = 0; isspace(s[i]); i++) // skip white space ; sign = (s[i] == '-') ? -1 : 1; if (s[i] == '+' || s[i] == '-') i++; for (val = 0.0; isdigit(s[i]); i++) val = 10.0 * val + (s[i] - '0'); if (s[i] == '.') i++; for (power = 1.0; isdigit(s[i]); i++) { val = 10.0 * val + (s[i] - '0'); power *= 10.0; } if (s[i] == 'e' || s[i] == 'E') { i++; signE = (s[i] == '-') ? -1 : 1; if (s[i] == '+' || s[i] == '-') i++; for (exp = 0; isdigit(s[i]); i++) exp = 10 * exp + (s[i] - '0'); return (sign * val / power) * powd(10, signE * exp); } return sign * val / power; } // very basic math.h version of pow. I would have just used the math.h function, but the exercise asks for the function to be called // atof and that conflicts with an existing function in math.h. So I just wrote this instead double powd(double base, double p) { int i; double result; result = 1.0; if (p > 0) for (i = 0; i < p; ++i) result *= base; else for (i = p; i < 0; ++i) result /= base; return result; }
Solution by Foowar
#include <ctype.h> #include <math.h> double atof(char s[]) { double val, power; int i, sign; int e, esign; for (i = 0; isspace(s[i]); i++) ; sign = (s[i] == '-') ? -1 : 1; if (s[i] == '+' || s[i] == '-') i++; for (val = 0.0; isdigit(s[i]); i++) val = 10.0 * val + (s[i] - '0'); if (s[i] == '.') i++; for (power = 1.0; isdigit(s[i]); i++) { val = 10.0 * val + (s[i] - '0'); power *= 10.0; } esign = 1; e = 0; if (tolower(s[i]) == 'e') { i++; if (s[i] == '-') { esign = -1; i++; } if (s[i] == '+') { i++; } for (; isdigit(s[i]); i++) { e *= 10; e += s[i] - '0'; } } return (sign * val / power) * pow(10, e * esign); }