I think I checked all the edge cases. If anyone thinks of an edge case that I missed please let me know in the comments and I will update this post. I tried to keep the error messages simple. If you disagree with this decision please feel free to change them as you see fit.
// Copyright (C) 2021 by cmwt
// Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#include <errno.h> // errno, ERANGE
#include <limits.h> // LONG_MAX, LONG_MIN
#include <stdbool.h> // true, false
#include <stddef.h> // ptrdiff_t
#include <stdio.h> // printf()
#include <stdlib.h> // strtol()
struct ResultToLong {
const char *err_msg;
long answer;
ptrdiff_t num_read;
bool is_func_success;
};
struct ResultToLong stringToLong(const char* start, int base) {
struct ResultToLong result = {NULL, 0, 0, false};
int save_errno = 0;
char *end = NULL;
if (base < 0 || base > 36) {
result.err_msg = "Bad base: expect (0 <= base <= 36)";
return result;
}
if (start == NULL) {
result.err_msg = "Bad start: expect (start != NULL)";
return result;
}
if (*start == '\0') {
result.err_msg = "Bad start: start empty (const char* start == \"\";)";
return result;
}
errno = 0;
result.answer = strtol(start, &end, base);
save_errno = errno;
if (result.answer == 0 && *(start - 1) != '0') {
result.err_msg = "Bad start: not a number";
result.num_read = end - start;
return result;
}
if (result.answer == LONG_MIN && save_errno == ERANGE) {
result.err_msg = "Bad start: result < LONG_MIN";
result.num_read = end - start;
return result;
}
if (result.answer == LONG_MAX && save_errno == ERANGE) {
result.err_msg = "Bad start: result > LONG_MAX";
result.num_read = end - start;
return result;
}
if (*end != '\0') {
result.err_msg = "Warning: number in start is not '\\0' terminated";
result.num_read = end - start;
result.is_func_success = true;
return result;
}
result.err_msg = "Success";
result.num_read = end - start;
result.is_func_success = true;
return result;
}
int main() {
struct ResultToLong result;
const char* str;
printf("Starting...\n\n");
str= NULL;
result = stringToLong(str, 0);
printf("Response message: %s\n", result.err_msg);
printf("Input string: %s\n", "<NULL>");
printf("Number of chars processed: %ld\n", result.num_read);
printf("Result: %ld\n\n", result.answer);
str= "";
result = stringToLong(str, 0);
printf("Response message: %s\n", result.err_msg);
printf("Input string: '%s'\n", str);
printf("Number of chars processed: %ld\n", result.num_read);
printf("Result: %ld\n\n", result.answer);
str= "42";
result = stringToLong(str, -1);
printf("Response message: %s\n", result.err_msg);
printf("Input string: '%s'\n", str);
printf("Number of chars processed: %ld\n", result.num_read);
printf("Result: %ld\n\n", result.answer);
str= "42";
result = stringToLong(str, 0);
printf("Response message: %s\n", result.err_msg);
printf("Input string: '%s'\n", str);
printf("Number of chars processed: %ld\n", result.num_read);
printf("Result: %ld\n\n", result.answer);
str= "42 ";
result = stringToLong(str, 0);
printf("Response message: %s\n", result.err_msg);
printf("Input string: '%s'\n", str);
printf("Number of chars processed: %ld\n", result.num_read);
printf("Result: %ld\n\n", result.answer);
str= " 42";
result = stringToLong(str, 0);
printf("Response message: %s\n", result.err_msg);
printf("Input string: '%s'\n", str);
printf("Number of chars processed: %ld\n", result.num_read);
printf("Result: %ld\n\n", result.answer);
str= "0x42";
result = stringToLong(str, 0);
printf("Response message: %s\n", result.err_msg);
printf("Input string: '%s'\n", str);
printf("Number of chars processed: %ld\n", result.num_read);
printf("Result: %ld\n\n", result.answer);
str= "042";
result = stringToLong(str, 0);
printf("Response message: %s\n", result.err_msg);
printf("Input string: '%s'\n", str);
printf("Number of chars processed: %ld\n", result.num_read);
printf("Result: %ld\n\n", result.answer);
str= "+9999999999999999999";
result = stringToLong(str, 0);
printf("Response message: %s\n", result.err_msg);
printf("Input string: '%s'\n", str);
printf("Number of chars processed: %ld\n", result.num_read);
printf("Result: %ld\n\n", result.answer);
str= "-9999999999999999999";
result = stringToLong(str, 0);
printf("Response message: %s\n", result.err_msg);
printf("Input string: '%s'\n", str);
printf("Number of chars processed: %ld\n", result.num_read);
printf("Result: %ld\n\n", result.answer);
str= "?";
result = stringToLong(str, 0);
printf("Response message: %s\n", result.err_msg);
printf("Input string: '%s'\n", str);
printf("Number of chars processed: %ld\n", result.num_read);
printf("Result: %ld\n\n", result.answer);
printf("Done.\n");
}
If you object to returning a struct of this size by value then pass a pointer to the struct as an additional argument, just don't forget to handle the case where the struct pointer is NULL
. My goal was to make this code easy to understand. You probbaly want combine checks and centralize setting the the return values.
Other Thoughts
I kinda wish strtol()
would also return where the number starts. You could figure this out by iterating back from the end pointer but it might be slow.