How to use `strtoul` to parse string where zero may be valid?
Asked Answered
S

4

8

According to the documentation for strtoul, regarding its return value...

This function returns the converted integral number as a long int value. If no valid conversion could be performed, a zero value is returned.

What if I'm parsing a user-supplied string of "0" where, for my application, "0" may be a valid entry? In that case it seems that I have no way to determine from using strtoul if a valid conversion was performed. Is there another way to handle this?

Smokeproof answered 13/12, 2018 at 14:29 Comment(2)
You could check endptr if it points to the begining?Bowe
There might be other invalid strings, such as asd123. You may also check if the first character of the string is a digit or no (if the string is not empty). All in all, Sourav Ghosh's answer seems more extensive.Obsequies
M
5

How to use strtoul to parse string where zero may be valid?

Any value returned from strtoul() may be from an expected string input or from other not so expected strings. Further tests are useful.

The following strings all return 0 from strtoul()

  • OK "0", "-0", "+0"
  • Not OK "", "abc"
  • Usually considered OK: " 0"
  • OK or not OK depending on goals: "0xyz", "0 ", "0.0"

strtoul() has the various detection modes.

int base = 10;
char *endptr;  //  Store the location where conversion stopped

errno = 0;
unsigned long y = strtoul(s, &endptr, base);

if (s == endptr) puts("No conversion");      // "", "abc"
else if (errno == ERANGE) puts("Overflow");
else if (*endptr) puts("Extra text after the number"); // "0xyz", "0 ", "0.0"
else puts("Mostly successful");

What is not yet detected.

  • Negative input. strtoul() effectively wraps around such that strtoul("-1", 0, 10) == ULONG_MAX). This issue is often missed in cursory documentation review.

  • Leading white space allowed. This may or may not be desired.


To also detect negative values:

// find sign
while (isspace((unsigned char) *s)) {
  s++;
}
char sign = *s;

int base = 10;
char *endptr;  //  Store the location where conversion stopped
errno = 0;
unsigned long y = strtoul(s, &endptr, base);

if (s == endptr) puts("No conversiosn");
else if (errno == ERANGE) puts("Overflow");
else if (*endptr) puts("Extra text after the number"); 
else if (sign == '-' && y != 0) puts("Negative value"); 
else puts("Successful");
Mandola answered 13/12, 2018 at 14:36 Comment(0)
P
9

Read further the man page:

Since strtoul() can legitimately return 0 or ULONG_MAX (ULLONG_MAX for strtoull()) on both success and failure, the calling program should set errno to 0 before the call, and then determine if an error occurred by checking whether errno has a nonzero value after the call.

Also, to handle another scenario, where no digits were read in the input. If this happens, strtol() sets the value of *endptr to that of the nptr. So, you should also check that the pointer values compare equal or not.

Parthenia answered 13/12, 2018 at 14:32 Comment(0)
M
5

How to use strtoul to parse string where zero may be valid?

Any value returned from strtoul() may be from an expected string input or from other not so expected strings. Further tests are useful.

The following strings all return 0 from strtoul()

  • OK "0", "-0", "+0"
  • Not OK "", "abc"
  • Usually considered OK: " 0"
  • OK or not OK depending on goals: "0xyz", "0 ", "0.0"

strtoul() has the various detection modes.

int base = 10;
char *endptr;  //  Store the location where conversion stopped

errno = 0;
unsigned long y = strtoul(s, &endptr, base);

if (s == endptr) puts("No conversion");      // "", "abc"
else if (errno == ERANGE) puts("Overflow");
else if (*endptr) puts("Extra text after the number"); // "0xyz", "0 ", "0.0"
else puts("Mostly successful");

What is not yet detected.

  • Negative input. strtoul() effectively wraps around such that strtoul("-1", 0, 10) == ULONG_MAX). This issue is often missed in cursory documentation review.

  • Leading white space allowed. This may or may not be desired.


To also detect negative values:

// find sign
while (isspace((unsigned char) *s)) {
  s++;
}
char sign = *s;

int base = 10;
char *endptr;  //  Store the location where conversion stopped
errno = 0;
unsigned long y = strtoul(s, &endptr, base);

if (s == endptr) puts("No conversiosn");
else if (errno == ERANGE) puts("Overflow");
else if (*endptr) puts("Extra text after the number"); 
else if (sign == '-' && y != 0) puts("Negative value"); 
else puts("Successful");
Mandola answered 13/12, 2018 at 14:36 Comment(0)
B
1

One solution would be to pass the address of a char pointer and check if it is pointing to the beginning of the string:

char *str = "0";
char *endptr;
unsgined long x = strtoul(str, &endptr, 10);

if(endptr == str)
{
    //Nothing was read
}
Bowe answered 13/12, 2018 at 14:36 Comment(0)
G
1

Consider the following function:

#include <stdlib.h>
#include <errno.h>
/* SPDX-Identifier: CC0-1.0 */

const char *parse_ulong(const char *src, unsigned long *to)
{
    const char    *end;
    unsigned long  val;

    if (!src) {
        errno = EINVAL;
        return NULL;
    }

    end = src;
    errno = 0;
    val = strtoul(src, (char **)(&end), 0);
    if (errno)
        return NULL;
    if (end == src) {
        errno = EINVAL;
        return NULL;
    }

    if (to)
       *to = val;

    return end;
}

This function parses the unsigned long in the string src, returning a pointer to the first unparsed character in src, with the unsigned long saved to *to. If there is an error, the function will return NULL with errno set to indicate the error.

If you compare the function to man 3 strtoul, you'll see it handles all error cases correctly, and only returns non-NULL when src yields a valid unsigned long. Especially see the Notes section. Also pay attention to how negative numbers are handled.

This same pattern works for strtol(), strtod(), strtoull().

Gerontology answered 13/12, 2018 at 14:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.