chux
chux

Reputation: 154230

`strtoul()` What is the correct return value for very "negative" strings?

This is an It’s OK to Ask and Answer Your Own Questions. I researched the issue, found the result curious, and posted my findings.


What value should "very negative" strings return from strtoul(): 1, ULONG_MAX or what?


strtol()

For strings representing numeric values like "123", strtol() behaves as expected. Strings (blue) [LONG_MIN ... LONG_MAX] convert to their expected long value. Strings (yellow) above LONG_MAX convert to LONG_MAX and set errno==ERANGE. Strings below LONG_MIN convert to LONG_MIN and set errno==ERANGE.

strtoul() positive

For strings representing numeric values like "123", strtoul() also behaves as expected. Strings (red) [0 ... ULONG_MAX] convert to their expected unsigned long value. Strings (green) above ULONG_MAX convert to ULONG_MAX and set errno==ERANGE.

If the subject sequence begins with a minus sign, the value resulting from the conversion is negated (in the return type). C17dr § 7.22.1.4 5

strtoul() negative

For strings (red) [-ULONG_MAX ... -1], the conversion negates the positive conversion and adds ULONG_MAX + 1 (like the typical assignment of a negative value to an unsigned) and does not set errno. This is a bit surprising, but that is how the specs defines it.

strtoul() very negative - the problem

For strings (green) less than-ULONG_MAX, I'd expect the conversion to be handled like smaller negative values described above: the conversion negates the positive conversion (ULONG_MAX due to overflow) and adds ULONG_MAX + 1. Expected result 1 (or maybe 0) with errno == ERANGE. Yet exercising strtoul() resulted in ULONG_MAX.

What is correct?

strtoul

Test code

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

long strtol_test(const char *s, int base) {
  printf("\n");
  int width = snprintf(NULL, 0, "%ld", LONG_MIN);
  printf("base:%2d \"%s\"\n", base, s);

  char *endptr_signed;
  errno = 0;
  long val_signed = strtol(s, &endptr_signed, base);
  int errno_signed = errno;

  char *endptr_unsigned;
  errno = 0;
  unsigned long val_unsigned = strtoul(s, &endptr_unsigned, base);
  int errno_unsigned = errno;

  if (val_signed < 0 || (unsigned long) val_signed != val_unsigned
      || endptr_signed != endptr_unsigned || errno_signed != errno_unsigned) {
    printf("  signed  val:%*ld end:%2td e:%s\n", width, val_signed,
        endptr_signed - s, strerror(errno_signed));
    printf("unsigned  val:%*lu end:%2td e:%s\n", width, val_unsigned,
        endptr_unsigned - s, strerror(errno_unsigned));
    return 1;
  }

  printf("    both  val:%*ld end:%2td e:%s\n", width, val_signed,
      endptr_signed - s, strerror(errno_signed));
  return 0;
}

int main() {
  char s[][50] = {"-ULONG_MAX1", "-ULONG_MAX", "LONG_MIN1", "LONG_MIN", "-1",
      "-0", "42", "LONG_MAX", "LONG_MAX1", "ULONG_MAX", "ULONG_MAX1", "x"};
  snprintf(s[0], sizeof *s, "-%lu", ULONG_MAX);
  s[0][strlen(s[0]) - 1]++;
  snprintf(s[1], sizeof *s, "-%lu", ULONG_MAX);
  snprintf(s[2], sizeof *s, "%ld", LONG_MIN);
  s[2][strlen(s[2]) - 1]++;
  snprintf(s[3], sizeof *s, "%ld", LONG_MIN);

  snprintf(s[7], sizeof *s, "%ld", LONG_MAX);
  snprintf(s[8], sizeof *s, "%ld", LONG_MAX);
  s[8][strlen(s[8]) - 1]++;
  snprintf(s[9], sizeof *s, "%lu", ULONG_MAX);
  snprintf(s[10], sizeof *s, "%lu", ULONG_MAX);
  s[10][strlen(s[10]) - 1]++;

  strcpy(s[11], s[0]);
  s[11][strlen(s[11]) - 1]++;

  int n = sizeof s / sizeof s[0];
  for (int i = 0; i < n; i++) {
    strtol_test(s[i], 0);
  }
}

Sample output

base: 0 "-18446744073709551616"
  signed  val:-9223372036854775808 end:21 e:Numerical result out of range
unsigned  val:18446744073709551615 end:21 e:Numerical result out of range

base: 0 "-18446744073709551615"
  signed  val:-9223372036854775808 end:21 e:Numerical result out of range
unsigned  val:                   1 end:21 e:No error

base: 0 "-9223372036854775809"
  signed  val:-9223372036854775808 end:20 e:Numerical result out of range
unsigned  val: 9223372036854775807 end:20 e:No error

base: 0 "-9223372036854775808"
  signed  val:-9223372036854775808 end:20 e:No error
unsigned  val: 9223372036854775808 end:20 e:No error

base: 0 "-1"
  signed  val:                  -1 end: 2 e:No error
unsigned  val:18446744073709551615 end: 2 e:No error

base: 0 "-0"
    both  val:                   0 end: 2 e:No error

base: 0 "42"
    both  val:                  42 end: 2 e:No error

base: 0 "9223372036854775807"
    both  val: 9223372036854775807 end:19 e:No error

base: 0 "9223372036854775808"
  signed  val: 9223372036854775807 end:19 e:Numerical result out of range
unsigned  val: 9223372036854775808 end:19 e:No error

base: 0 "18446744073709551615"
  signed  val: 9223372036854775807 end:20 e:Numerical result out of range
unsigned  val:18446744073709551615 end:20 e:No error

base: 0 "18446744073709551616"
  signed  val: 9223372036854775807 end:20 e:Numerical result out of range
unsigned  val:18446744073709551615 end:20 e:Numerical result out of range

base: 0 "-18446744073709551617"
  signed  val:-9223372036854775808 end:21 e:Numerical result out of range
unsigned  val:18446744073709551615 end:21 e:Numerical result out of range

Upvotes: 2

Views: 522

Answers (1)

chux
chux

Reputation: 154230

A range error result is always ULONG_MAX for strtoul().

This is unlike strtol() whose return value is LONG_MIN or LONG_MAX, depending on the sign of the range error.

It is fairly simple, for strings like "-18446744073709551616", since the correct (positive) value is outside the range, ULONG_MAX is returned and errno is set to ERANGE. No provision exists on a range error for strtoul() to provide any other answer.

... If the correct value is outside the range of representable values, ... ULONG_MAX, ... is returned (according to the return type and sign of the value, if any), and the value of the macro ERANGE is stored in errno. C17dr § 7.22.1.4 8

Thus strtoul() produces a non-error value when the string is in the [-ULONG_MAX ... +ULONG_MAX] range. The converted result is an unsigned long [0 ... ULONG_MAX]. An error result is always ULONG_MAX.

Upvotes: 5

Related Questions