1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
|
#include "c23-fallback.h"
#include <errno.h>
//#include <limits.h>
#include <stdio.h>
//#include <stdlib.h>
//#include <string.h>
#include <assert.h>
/* Supposes that lowercase characters are contiguous. */
static_assert('z'-'a' == 25,
"alphabetic characters not contiguous");
#include <ctype.h>
/* Converts an alphanumeric digit to an unsigned */
/* '0' ... '9' => 0 .. 9u */
/* 'A' ... 'Z' => 10 .. 35u */
/* 'a' ... 'z' => 10 .. 35u */
/* Other values => Greater */
unsigned hexatridecimal(int a) {
if (isdigit(a)) {
/* This is guaranteed to work: decimal digits
are consecutive, and isdigit is not
locale dependent. */
return a - '0';
} else {
/* Leaves a unchanged if it is not lowercase */
a = toupper(a);
/* Returns value >= 36 if not Latin uppercase */
return (isupper(a)) ? 10 + (a - 'A') : -1;
}
}
unsigned long Strtoul_inner(char const s[static 1],
size_t i,
unsigned base) {
unsigned long ret = 0;
while (s[i]) {
unsigned c = hexatridecimal(s[i]);
if (c >= base) break;
/* Maximal representable value for 64 bit is
3w5e11264sgsf in base 36 */
if (ULONG_MAX/base < ret) {
ret = ULONG_MAX;
errno = ERANGE;
break;
}
ret *= base;
ret += c;
++i;
}
return ret;
}
static unsigned find_prefix(char const s[static 1], size_t i, char const t[static 1]) {
size_t ret = 0;
while (s[i+ret] && (tolower(s[i+ret]) == tolower(t[ret]))) {
++ret;
}
return ret;
}
unsigned long Strtoul(char const s[static 1], unsigned base) {
if (base > 36u) { /* Tests if base */
errno = EINVAL; /* Extends the specification */
return ULONG_MAX;
}
size_t i = strspn(s, " \f\n\r\t\v"); /* Skips spaces */
bool switchsign = false; /* Looks for a sign */
switch (s[i]) {
case '-' :
switchsign = true;
[[fallthrough]];
case '+' :
++i;
}
/* Test if there is anything left in the string. */
if (!s[i]) return 0;
if (!base || base == 16 || base == 2) { /* use prefix */
size_t adj = find_prefix(s, i, "0x");
switch (adj) {
case 2: // There is a 0x or a 0X prefix
if (!base || base == 16) base = 16;
// If we are looking for another base, the x is the end
else return 0;
break;
case 1: // There is a 0 prefix
adj = find_prefix(s, i, "0b");
switch (adj) {
case 1: // There is only a 0 prefix
if (!base) base = 8;
break;
default: // There is a 0b or a 0B prefix
if (!base || base == 2) base = 2;
// If we are looking for another base, the b is the end
else return 0;
break;
}
break;
default:
if (!base) base = 10;
break;
}
i += adj;
}
/* Test again, maybe the prefix 0 was the only digit. */
if (!s[i]) return 0;
/* Now, starts the real conversion */
unsigned long ret = Strtoul_inner(s, i, base);
return (switchsign) ? -ret : ret;
}
char* char36(unsigned long n, unsigned base, char buf[static 256]) {
if (base > 36) {
strcpy(buf, "EINVAL");
} else {
unsigned i = 256;
while (n) {
--i;
buf[i] = n % base;
n /= base;
if (buf[i] < 10) {
buf[i] += '0';
} else {
buf[i] += 'a' - 10;
}
}
memmove(buf, &buf[i], 256-i);
}
return buf;
}
int main(int argc, char* argv[argc+1]) {
static unsigned const code[] = { 0, 8, 10, 16, 36, 37, };
for (unsigned j = 0; j < sizeof code/ sizeof code[0]; ++j) {
printf("%u:", code[j]);
for (int i = 1; i < argc; ++i) {
unsigned long res = Strtoul(argv[i], code[j]);
if (res == ULONG_MAX && errno) {
puts("");
fprintf(stderr, "invalid conversion with base %u and \"%s\"\n", code[j], argv[i]);
perror("error");
errno = 0;
} else {
printf("\t%lu", res);
}
}
puts("");
if (code[j]) printf("%u:\tULONG_MAX %s\n", code[j], char36(ULONG_MAX, code[j], (char[256]){ }));
}
return EXIT_SUCCESS;
}
|