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 , Pilcrow and Adam89.
/* ** 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
I have replaced my previous effort.. This one contains a basic validity test. All input is via the keyboard... Pilcrow 07:54, 13 September 2011 (UTC)
#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; }
blob84 14:39, 19 november 2012 (UTC)
/* 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; }
Adam89 (talk) 22:00, 21 May 2013 (UTC): 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.*/ }










