I'm trying to understand the strtod()
function, and how I can handle any user input given to the function. I assume I tested for everything, however I am hoping for a review to make sure and to catch anything I missed. (I know I should separate everything into functions, however, I am just trying to understand the nature of the function, therefore I left everything in main
.)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <float.h>
#include <stdbool.h>
/*@param MAXSIZE max number of chars that are converted to a 64bit double*/
#define MAXSIZE 1077
int main() {
char str[MAXSIZE];
printf("Enter a rational number: ");
for (;;) {
if (fgets(str, sizeof str, stdin) != NULL) {
if (strchr(str, '\n')) {
str[strcspn(str, "\n")] = '0円';
}
if (str[0] != '0円') {
break;
}
printf("\nTry again: ");
}
}
char* endptr = NULL;
errno = 0;
double number = strtod(str, &endptr);
//passes over trailing whitespace
for (; isspace(*endptr); ++endptr);
if (errno == ERANGE) {
fprintf(stderr, "Error out of range...\n");
}
else if (*endptr != '0円') {
fprintf(stderr, "error could not convert: %s\n", str);
}
else {
printf("Your string was converted into the rational number: %lf\n", number);
}
printf("Your string was: %s\n", str);
printf("Press any key to continue...\n");
getch();
}
2 Answers 2
str[strcspn(str, "\n")] = '0円';
is an overkill. fgets
reads (at most) one string, that is there is at most one '\n'
in the buffer, and strchr
will return its position. Consider
char * newline = strchr(str, '\n');
if (!newline) {
.... // handle input too long condition
} else if (newline == str) {
.... // handle empty input condition
}
*newline = '0円';
break;
Notice that even newline = '0円'
is redundant: '\n'
is a whitespace.
Do not invent error messages. This is what strerror
and perror
are for. Also keep in mind that strtod
may set errno
to errors other than ERANGE
(some implementations use EINVAL
as well). Consider
if (errno != 0) {
perror("strtod");
}
-
\$\begingroup\$ Thank you! So if errno is not zero, it is some error value and perror will return what error occured? \$\endgroup\$chris360– chris3602016年10月09日 02:32:46 +00:00Commented Oct 9, 2016 at 2:32
-
\$\begingroup\$ I also have another question, perror(errno) does not compile, however perror(NULL) works as expected, was that a typo or am I missing something? Another thing, can I still use my fprintf() statement? It works fine, I know i'm customizing my own error message but is there a reason I shouldn't? If yes how else could I implement it? Thank you! \$\endgroup\$chris360– chris3602016年10月09日 03:14:14 +00:00Commented Oct 9, 2016 at 3:14
-
\$\begingroup\$ @chris360
perror
will print the standard error message.strerror
will return an error message, so you may chose to print it in a more fancy manner. Also yes, there was a typo, sorry. \$\endgroup\$vnp– vnp2016年10月09日 03:14:14 +00:00Commented Oct 9, 2016 at 3:14 -
\$\begingroup\$ How is
str[strcspn(str, "\n")] = '0円';
overkill? Certainly a popular answer to Removing trailing newline character from fgets() input. Should it be 2-3 times slower thanstrchr()
method, that time savings is certainly swamped by thefgets()
itself. Also lack of'\n'
inif (!newline) {
is caused by 2 other reasons too beside "input too long": prior embedded null character and end-of-file occurring. True about no need to drop the'\n'
asfor (; isspace(*endptr); ++endptr);
will handle that. \$\endgroup\$chux– chux2016年11月08日 18:16:00 +00:00Commented Nov 8, 2016 at 18:16
- With
strtod()
,if (errno == ERANGE) {
is problematic.
When the input string represents a value exceeding +/- DBL_MAX
, errno == ERANGE
is set - no problem there.
When the input string represents a tiny non-zero value -DBL_MIN < x < DBL_MIN
, errno == ERANGE
might be set. It is implementation-defined.
If the result underflows ..., the functions return a value whose magnitude is no greater than the smallest normalized positive number in the return type; whether
errno
acquires the valueERANGE
is implementation-defined. C11 §7.22.1.3 10
These is no truly robust solution. The best is to not flag an error when the answer is tiny.
if (errno == ERANGE) {
if (number >= -DBL_MIN && number <= DBL_MIN) {
errno = 0;
}
}
Printing a
double
using"%f"
does not well present large numbers nor tiny ones. Suggest"%e"
or"%g"
1077
is not the max number ofchar
that are converted to a 64bit double. There is no C specified upper bound. Although1077
is certainly generous, buffer size needed to print-DBL_TRUE_MIN
is 1077 + 1 and so would need 1077 + 1 (\n
) + 1 (0円
) to read withfgets()
for IEEE binary64.
IAC, the buffer size needed is not the number of character used to print a double into a string, but the number of characters needed when reading. Depending on coding goals, this could be as small as about 400 (extras characters will not likely make a difference) or pedantically: unlimited.
str[strcspn(str, "\n")] = '0円';
is sufficient.// if (strchr(str, '\n')) { // str[strcspn(str, "\n")] = '0円'; // } str[strcspn(str, "\n")] = '0円';
OP's code fails to detect input of all spaces because "passes over trailing whitespace" may be passing over leading white-spaces.
double number = strtod(str, &endptr); // add if (str == endptr) fail();
I'll offer a more robust "parsing a string into a double" code that allows leading/trailing whitespace taking into account the above §7.22.1.3 10 spec.
// return true on success
bool string_double(const char * restrict str, double * restrict dest) {
if (str == NULL) return false; // Optional NULL check
errno = 0;
char *endptr;
double y = strtod(str, &endptr);
if (errno == ERANGE) {
if (fabs(y) > 1.0) {
// Alternatives for handling overflow exist,
// it depends on coding goals.
// Go for simply for now.
return false; // overflow
}
// Let all underflow pass as code can't portably detect it.
// Code has no real portable options. C11 §7.22.1.3 10
// Suggest `y = 0.0` for highest portability
// Otherwise leave `y` as is for most informative.
y = 0.0; // Optional
} else if (errno) {
// Should some other errno occur,
// (no other errno value are specified in the standard for strod()),
// consider returning.
return false;
}
if (str == endptr) return false; // no conversion, e.g. all spaces
// consume trailing white-space
while (isspace((unsigned char) *endptr)) endptr++;
if (*endptr) return false; // trailing junk
if (dest) *dest = y;
return true;
}
1/3
here. "Decimal" may be a better choice. (not an answer, as it's tangential to the requested review) \$\endgroup\$