7

I am using strtod( ) function to extract an environment variable as a string, and then changing it to double using strtod:

enter code here
 char strEnv[32];
 strncpy(strEnv, getenv("LT_LEAK_START"), 31);
 // How to make sure before parsing that env LT_LEAK_START is indeed a number?
 double d = strtod(strEnv, NULL);

Now i want to make sure that this number entered by user is a number and not a string or special character. How can i make sure of that?

A code snippet would be of great help.

Thanks in advance.

RajSanpui
  • 11,556
  • 32
  • 79
  • 146

7 Answers7

18

The 2nd argument to the strtod function is useful.

char *err;
d = strtod(userinput, &err);
if (*err == 0) { /* very probably ok */ }
if (!isspace((unsigned char)*err)) { /* error */ }

Edit: examples added

The strtod function tries to convert the initial portion of the 1st argument to a double and stops either when there are no more chars, or there is a char that can't be used to make a double.

input         result
----------    ----------------------------
"42foo"       will return 42
              and leave err pointing to the "foo" (*err == 'f')

"     4.5"    will return 4.5
              and leave err pointing to the empty string (*err == 0)

"42         " will return 42
              and leave `err` pointing to the spaces (*err == ' ')
pmg
  • 106,608
  • 13
  • 126
  • 198
  • @pmg: Thanks a ton. But i am not exactly able to understand, what exactly you are trying to do with err? – RajSanpui Apr 07 '11 at 12:34
  • @pmg: So, separately, shall we check all for special characters, etc, isalpha, like that? – RajSanpui Apr 07 '11 at 12:35
  • @pmg: Thanks a lot for the examples. Then why check for spaces? CAn't we directly check for `(*err==0)`? – RajSanpui Apr 07 '11 at 12:43
  • The input `"4 2"` will return 4 and leave `foo` pointing to `" 2"`. I think that is an invalid number, just checking for 1 space in *err is not enough. – pmg Apr 07 '11 at 12:46
  • @pmg: Thanks, your is the best explanation. Many others were also useful. – RajSanpui Apr 07 '11 at 12:49
  • @pmg However I still don't agree with your answer. if you have `" foo", `*err != 0` and `isspace((unsigned char)*err) == true` – log0 Apr 07 '11 at 14:26
  • @Ugo: you are correct. My code above is incomplete. It doesn't correctly validate for " foo" or "" (empty string). It was only meant as an example usage of the 2nd argument to `strtod()`. – pmg Apr 07 '11 at 14:32
3

man strtod: If no conversion is performed, zero is returned and the value of nptr is stored in the location referenced by endptr.

char * endptr;
double d = strtod(strEnv, &endptr);
if (strEnv == endptr)
   /* invalid number */
else
   ...
log0
  • 10,489
  • 4
  • 28
  • 62
  • You haven't made any checks for special characters? – RajSanpui Apr 07 '11 at 12:40
  • 1
    @kingsmasher1 if there is a special character. It is not a valid number anyway. So the conversion fails and endptr is set to strEnv. – log0 Apr 07 '11 at 12:42
  • 1
    Your answer is misleading: there may be cases where `strEnv != endptr` and the number was valid, for example `"42 "` (space at the end) – pmg Apr 07 '11 at 13:42
  • @pgm in fact you always have `strEnv != endptr` if the number is valid. – log0 Apr 07 '11 at 14:22
3

Surely you could do worse than just reading the man page for strtod() and acting upon that. E.g. on my Linux system it says:

RETURN VALUE
       These functions return the converted value, if any.

       If  endptr  is  not  NULL,  a pointer to the character after the last character used in the conversion is stored in the location referenced by
       endptr.

       If no conversion is performed, zero is returned and the value of nptr is stored in the location referenced by endptr.

       If the correct value would cause overflow, plus or minus HUGE_VAL (HUGE_VALF, HUGE_VALL) is returned (according to the sign of the value), and
       ERANGE is stored in errno.  If the correct value would cause underflow, zero is returned and ERANGE is stored in errno.

That pretty much tells you what you need to do in order to handle errors. Also, like Johann Gerell said, you also need to check whether getenv() succeeded; a similar approach works there, i.e. check the man page and write error handling code according to that.

janneb
  • 36,249
  • 2
  • 81
  • 97
  • It doesn't tell you all you need to know at all if you want to know if the string is valid. Returning 0.0 on error is fine unless of course 0.0 is a valid floating point value! – Jonathan Wood Sep 16 '19 at 15:06
  • 1
    @JonathanWood: Sure it does; read the man page more carefully! – janneb Sep 17 '19 at 08:51
  • @JonathanWood: There are many aspects of C and the C standard library that can be considered flawed, but this function isn't particularly high on the list. As for your question, it's right there in the part I quoted: "If no conversion is performed, zero is returned and the value of nptr is stored in the location referenced by endptr." – janneb Sep 17 '19 at 12:25
  • @JonathanWood: As for what a "man page" is, the first search engine hit is https://en.wikipedia.org/wiki/Man_page , which explains it pretty well. (Also note the question is tagged "linux", so the fact that non-Unix systems might not have man pages isn't particularly relevant here). – janneb Sep 17 '19 at 12:28
  • 1
    @janneb Your comment is completely correct regarding the availabilities of man pages under Linux, even as far as the lowly Raspberry Pi. That said, I encountered this question in the context of an app being built on a Windows system to run on an AVR microcontroller. Have no fear, the man pages are available: a quick Duckduckgo search for "man strtod: turns up the man page. They really are incredibly easy to find, no matter what. – dgnuff May 25 '23 at 00:55
1
  • First, check the return value of getenv - if it's NULL, then that environment variable doesn't exist.
  • Second, if the return value of getenv isn't NULL, then you have the value, as a string.
  • Third, don't set the char ** endptr parameter of strtod to NULL, but use it to check the validity of the converted value, also check for 0.0.
Johann Gerell
  • 24,991
  • 10
  • 72
  • 122
0

That second argument to strtod, which you've set to NULL, can be a pointer-to-pointer-to-char; the pointer-to-char that it points to will get set to the character after the last thing strtod managed to parse. If that's the end of the string, or at least there's nothing after it but whitespace, then what you had was a number. Otherwise, it was something else.

Gareth McCaughan
  • 19,888
  • 1
  • 41
  • 62
0

I don't know much about this language but I do know that strtod() will return 0.0 if the input is wrong. Maybe you could use a regular expression to validate the input string is a number.

David
  • 1,101
  • 5
  • 19
  • 38
0

OP's code has issues:

getenv() may return NULL

Consider a null pointer test of the result.

Not certainly a string

 char strEnv[32];
 strncpy(strEnv, getenv("LT_LEAK_START"), 31);

 // strEnv is not certainly a string as it may lack a null character.
 strEnv[31] = 0; // Add

The end pointer of strtod() is useful, yet deserves more testing

 // Conversion problems not detected.  See following.
 double d = strtod(strEnv, NULL);

char *endptr;
errno = 0;
double d = strtod(strEnv, &endptr);
if (d == endptr) {
  return Error_No_conversion;  // Like "", "+", "-.", "abc"
}
// Tolerate trailing white-space as leading space is OK
while (isspace(((unsigned char *)endptr)[0])) {
  endptr++;
}
if (*endptr) {
  return Error_Junk_after_number;  // Like "876 - 5309"
}

// Optional pedantic testing.
if (errno == ERANGE) {
  if (fabs(d) > 1.0) {
    // Usually this is OK to just continue;
    // `d` will have the signed value of HUGE_VAL (DBL_MAX or infinity)
    ; // return Error_Number_too_large;
  } else {
    // Usually this is OK to just continue;
    // `d` will have the signed value of DBL_MIN or 0.0 or some small value
    ; // return Error_Number_too_small;
  }
} else if (errno) {
  // Usually this is OK to just continue;
  ; return Error_Implementation_specific_error;
}

// Success, now use `d`.

So far, this answer does not fail "" nor "123 456".

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256