solidc
Robust collection of general-purpose cross-platform C libraries and data structures designed for rapid and safe development in C
Loading...
Searching...
No Matches
str_to_num.c
1#include "../include/str_to_num.h"
2
3#include "../include/str_utils.h"
4
5#include <errno.h> // for errno, ERANGE
6#include <float.h> // for FLT_EPSILON, DBL_EPSILON, FLT_MAX, DBL_MAX
7#include <inttypes.h> // for strtoimax, strtoumax, intmax_t, uintmax_t
8#include <limits.h> // for INT_MAX, UINT_MAX, etc.
9#include <math.h>
10#include <stdbool.h> // for bool, true, false
11#include <stdio.h> // for fprintf, stderr
12#include <stdlib.h> // for strtod, strtof
13#include <string.h> // for strlen
14
16static inline StoError validate_and_parse_signed(const char* str, int base, intmax_t* result) {
17 if (str == NULL || result == NULL) {
18 return STO_INVALID;
19 }
20
21 char* endptr = NULL;
22 errno = 0;
23 *result = strtoimax(str, &endptr, base);
24
25 // Check for standard ERANGE (overflow of intmax_t)
26 if (errno == ERANGE) {
27 return STO_OVERFLOW;
28 }
29
30 // Check for invalid input (no conversion or partial conversion)
31 if (endptr == str || *endptr != '\0') {
32 return STO_INVALID;
33 }
34
35 return STO_SUCCESS;
36}
37
39static inline StoError validate_and_parse_unsigned(const char* str, int base, uintmax_t* result) {
40 if (str == NULL || result == NULL) {
41 return STO_INVALID;
42 }
43
44 // Skip whitespace to correctly find the sign
45 const char* p = str;
46 while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r' || *p == '\v' || *p == '\f') {
47 p++;
48 }
49
50 // Check for negative input manually.
51 // Standard strtoumax wraps negative numbers (e.g. "-1" -> UINTMAX_MAX).
52 // For a strict "string to unsigned" conversion, negative input is an underflow.
53 if (*p == '-') {
54 return STO_UNDERFLOW;
55 }
56
57 char* endptr = NULL;
58 errno = 0;
59 *result = strtoumax(str, &endptr, base);
60
61 // Check for standard conversion errors (overflow of uintmax_t)
62 if (errno == ERANGE) {
63 return STO_OVERFLOW;
64 }
65
66 // Check for invalid input (no conversion or partial conversion)
67 if (endptr == str || *endptr != '\0') {
68 return STO_INVALID;
69 }
70
71 return STO_SUCCESS;
72}
73// NOLINTBEGIN(bugprone-macro-parentheses)
74
79#define IMPLEMENT_SIGNED_CONVERSION(func_name, type_name, type_max, type_min) \
80 StoError func_name(const char* str, type_name* result) { \
81 if (result == NULL) { \
82 return STO_INVALID; \
83 } \
84 \
85 intmax_t temp; \
86 StoError err = validate_and_parse_signed(str, 10, &temp); \
87 if (err != STO_SUCCESS) { \
88 return err; \
89 } \
90 \
91 /* Range check before casting */ \
92 if (temp > (intmax_t)(type_max) || temp < (intmax_t)(type_min)) { \
93 return STO_OVERFLOW; \
94 } \
95 \
96 *result = (type_name)temp; \
97 return STO_SUCCESS; \
98 }
99
104#define IMPLEMENT_UNSIGNED_CONVERSION(func_name, type_name, type_max) \
105 StoError func_name(const char* str, type_name* result) { \
106 if (result == NULL) { \
107 return STO_INVALID; \
108 } \
109 \
110 uintmax_t temp; \
111 StoError err = validate_and_parse_unsigned(str, 10, &temp); \
112 if (err != STO_SUCCESS) { \
113 return err; \
114 } \
115 \
116 /* Range check before casting */ \
117 if (temp > (uintmax_t)(type_max)) { \
118 return STO_OVERFLOW; \
119 } \
120 \
121 *result = (type_name)temp; \
122 return STO_SUCCESS; \
123 }
124
126#define IMPLEMENT_SIGNED_BASE_CONVERSION(func_name, type_name, type_max, type_min) \
127 StoError func_name(const char* str, int base, type_name* result) { \
128 if (result == NULL) { \
129 return STO_INVALID; \
130 } \
131 \
132 intmax_t temp; \
133 StoError err = validate_and_parse_signed(str, base, &temp); \
134 if (err != STO_SUCCESS) { \
135 return err; \
136 } \
137 \
138 /* Range check before casting */ \
139 if (temp > (intmax_t)(type_max) || temp < (intmax_t)(type_min)) { \
140 return STO_OVERFLOW; \
141 } \
142 \
143 *result = (type_name)temp; \
144 return STO_SUCCESS; \
145 }
146
148#define IMPLEMENT_UNSIGNED_BASE_CONVERSION(func_name, type_name, type_max) \
149 StoError func_name(const char* str, int base, type_name* result) { \
150 if (result == NULL) { \
151 return STO_INVALID; \
152 } \
153 \
154 uintmax_t temp; \
155 StoError err = validate_and_parse_unsigned(str, base, &temp); \
156 if (err != STO_SUCCESS) { \
157 return err; \
158 } \
159 \
160 /* Range check before casting */ \
161 if (temp > (uintmax_t)(type_max)) { \
162 return STO_OVERFLOW; \
163 } \
164 \
165 *result = (type_name)temp; \
166 return STO_SUCCESS; \
167 }
168
169// NOLINTEND(bugprone-macro-parentheses)
170
171// Generate all the integer conversion functions using macros
172IMPLEMENT_SIGNED_CONVERSION(str_to_i8, int8_t, INT8_MAX, INT8_MIN)
173IMPLEMENT_UNSIGNED_CONVERSION(str_to_u8, uint8_t, UINT8_MAX)
174
175IMPLEMENT_SIGNED_CONVERSION(str_to_i16, int16_t, INT16_MAX, INT16_MIN)
176IMPLEMENT_UNSIGNED_CONVERSION(str_to_u16, uint16_t, UINT16_MAX)
177
178IMPLEMENT_SIGNED_CONVERSION(str_to_i32, int32_t, INT32_MAX, INT32_MIN)
179IMPLEMENT_UNSIGNED_CONVERSION(str_to_u32, uint32_t, UINT32_MAX)
180
181IMPLEMENT_SIGNED_CONVERSION(str_to_i64, int64_t, INT64_MAX, INT64_MIN)
182IMPLEMENT_UNSIGNED_CONVERSION(str_to_u64, uint64_t, UINT64_MAX)
183
184IMPLEMENT_SIGNED_CONVERSION(str_to_int, int, INT_MAX, INT_MIN)
185IMPLEMENT_UNSIGNED_CONVERSION(str_to_uint, unsigned int, UINT_MAX)
186
187IMPLEMENT_SIGNED_CONVERSION(str_to_long, long, LONG_MAX, LONG_MIN)
188IMPLEMENT_UNSIGNED_CONVERSION(str_to_ulong, unsigned long, ULONG_MAX)
189
190// Base conversion variants
191IMPLEMENT_SIGNED_BASE_CONVERSION(str_to_int_base, int, INT_MAX, INT_MIN)
192IMPLEMENT_SIGNED_BASE_CONVERSION(str_to_long_base, long, LONG_MAX, LONG_MIN)
193IMPLEMENT_UNSIGNED_BASE_CONVERSION(str_to_ulong_base, unsigned long, ULONG_MAX)
194
195// Special case for uintptr_t (no upper bound check needed as it can hold any uintmax_t value)
196StoError str_to_uintptr(const char* str, uintptr_t* result) {
197 if (result == NULL) {
198 return STO_INVALID;
199 }
200
201 uintmax_t temp = 0;
202 StoError err = validate_and_parse_unsigned(str, 10, &temp);
203 if (err != STO_SUCCESS) {
204 return err;
205 }
206
207 // uintptr_t should be able to hold any valid pointer value
208 // On most platforms, uintptr_t == uintmax_t, but we cast safely
209 *result = (uintptr_t)temp;
210 return STO_SUCCESS;
211}
212
213StoError str_to_float(const char* str, float* result) {
214 if (str == NULL || result == NULL) {
215 return STO_INVALID;
216 }
217 char* endptr = NULL;
218 errno = 0;
219 *result = strtof(str, &endptr);
220 if (endptr == str || *endptr != '\0') return STO_INVALID;
221 if (errno == ERANGE) {
222 if (isinf(*result) || fabsf(*result) > FLT_MAX) return STO_OVERFLOW;
223 if (*result == 0.0f || fabsf(*result) < FLT_MIN) return STO_UNDERFLOW;
224 }
225
226 return STO_SUCCESS;
227}
228
229StoError str_to_double(const char* str, double* result) {
230 if (str == NULL || result == NULL) {
231 return STO_INVALID;
232 }
233 char* endptr = NULL;
234 errno = 0;
235 *result = strtod(str, &endptr);
236 if (endptr == str || *endptr != '\0') return STO_INVALID;
237 if (errno == ERANGE) {
238 if (isinf(*result) || fabs(*result) > DBL_MAX) return STO_OVERFLOW;
239 if (*result == 0.0 || fabs(*result) < DBL_MIN) return STO_UNDERFLOW;
240 }
241 return STO_SUCCESS;
242}
243
245typedef struct {
246 const char* str;
247 bool value;
248} bool_mapping_t;
249
250static const bool_mapping_t BOOL_MAPPINGS[] = {
251 {"true", true}, {"false", false}, {"yes", true}, {"no", false},
252 {"on", true}, {"off", false}, {"1", true}, {"0", false},
253};
254
255static const size_t BOOL_MAPPINGS_COUNT = sizeof(BOOL_MAPPINGS) / sizeof(BOOL_MAPPINGS[0]);
256
257StoError str_to_bool(const char* str, bool* result) {
258 if (str == NULL || result == NULL) {
259 return STO_INVALID;
260 }
261
262 // Use lookup table for efficient case-insensitive matching
263 for (size_t i = 0; i < BOOL_MAPPINGS_COUNT; i++) {
264 if (strcasecmp(str, BOOL_MAPPINGS[i].str) == 0) {
265 *result = BOOL_MAPPINGS[i].value;
266 return STO_SUCCESS;
267 }
268 }
269
270 return STO_INVALID;
271}
272
273const char* sto_error_string(StoError code) {
274 switch (code) {
275 case STO_SUCCESS:
276 return "Conversion successful";
277 case STO_INVALID:
278 return "Invalid input string or null pointer";
279 case STO_OVERFLOW:
280 return "Numeric overflow";
281 case STO_UNDERFLOW:
282 return "Numeric overflow";
283 default:
284 return "Unknown error code";
285 }
286}
StoError str_to_bool(const char *str, bool *result)
Converts a string to a boolean. Valid inputs are "true", "false", "yes", "no", "1",...
Definition str_to_num.c:257
StoError str_to_ulong_base(const char *str, int base, unsigned long *result)
Converts a string to an unsigned long with a specified base.
StoError str_to_int_base(const char *str, int base, int *result)
Converts a string to an int with a specified base.
StoError str_to_float(const char *str, float *result)
Converts a string to a float.
Definition str_to_num.c:213
StoError str_to_ulong(const char *str, unsigned long *result)
Converts a string to an unsigned long.
StoError str_to_u64(const char *str, uint64_t *result)
Converts a string to a uint64_t.
StoError str_to_i64(const char *str, int64_t *result)
Converts a string to an int64_t.
StoError str_to_u8(const char *str, uint8_t *result)
Converts a string to a uint8_t.
StoError str_to_uint(const char *str, unsigned int *result)
Converts a string to an unsigned int.
StoError str_to_u32(const char *str, uint32_t *result)
Converts a string to a uint32_t.
StoError str_to_double(const char *str, double *result)
Converts a string to a double.
Definition str_to_num.c:229
StoError str_to_long_base(const char *str, int base, long *result)
Converts a string to a long with a specified base.
StoError str_to_u16(const char *str, uint16_t *result)
Converts a string to a uint16_t.
const char * sto_error_string(StoError code)
Definition str_to_num.c:273
StoError str_to_i32(const char *str, int32_t *result)
Converts a string to an int32_t.
StoError str_to_i16(const char *str, int16_t *result)
Converts a string to an int16_t.
StoError str_to_i8(const char *str, int8_t *result)
Converts a string to an int8_t.
StoError str_to_long(const char *str, long *result)
Converts a string to a long.
StoError str_to_int(const char *str, int *result)
Converts a string to an int.