Interface
static char temp[10];
⋮
return temp;
Returning a pointer to a static
member can be a problem. If clients aren't specifically warned, then they may inadvertently overwrite the storage before using it (consider printf("%s%s", itoa(1), itoa(2))
as a minimal example).
In any case, such functions are not safely usable from multiple threads, and writing single-threaded code is an onerous limitation in the modern world.
One alternative is to allocate memory, but that obviously carries a huge performance penalty.
The usual approach for such functions is for the caller to pass appropriate storage. The traditional interface is void itoa(int n, char *buf)
, but I would strongly recommend updating to imitate snprintf()
with something like size_t itoa(int n, char *buf, size_t bufsize)
- where the return value represents the number of characters that would be written, even if greater than bufsize
.
The getdigits()
function shouldn't need external linkage, I think, so declare it static
.
It returns 1
for all negative numbers. The way to fix that is to convert all positive inputs to negative ones (note that the other way around does not work, since -INT_MIN
isn't necessarily valid, but -INT_MAX
always is).
int get_digits(int num)
{
if (num > 0) {
/* strip the leading '-' */
return get_digits(-num) - 1;
}
if(num > -10)
return 2;
if(num > -100)
return 3;
⋮
This function makes assumptions about INT_MAX
which are not safe: If INT_MAX
is less than 1000000000, then we have an undefined conversion, and if it's greater than 10000000000, we will return a value that's too small.
Since INT_MAX
is guaranteed to be at least 10000, we can make a portable version:
int get_digits(int num)
{
if (num > 0) {
/* strip the leading '-' */
return get_digits(-num) - 1;
}
int digit_count = 1; /* leading '-' */
while (num <= -10000) {
digit_count += 4;
num /= 10000;
}
if (num <= -100) {
digit_count += 2;
num /= 100;
}
if (num <= -10) {
digit_count += 1;
}
return digit_count;
}
However, counting the digits in advance shouldn't be necessary - we can work backwards, then reverse the result in-place for less cost:
#include <stdlib.h>
size_t itoa(int n, char *buf, size_t bufsize)
{
if (n == 0) {
if (bufsize > 1) {
buf[0] = '0';
buf[1] = '\0';
}
return 1;
}
size_t i = 0;
size_t digits = 0;
if (n < 0) {
while (n) {
int r = n % 10;
n /= 10;
if (r > 0) {
/* non-standard compiler */
r -= 10;
++n;
}
if (buf && i < bufsize - 1) {
buf[i++] = (char)('0' - r);
}
++digits;
}
if (buf && i < bufsize - 1) {
buf[i++] = '-';
}
++digits;
} else {
/* n > 0 */
while (n) {
int r = n % 10;
n = n / 10;
if (buf && i < bufsize - 1) {
buf[i++] = (char)('0' + r);
}
++digits;
}
}
if (buf && i < bufsize - 1) {
buf[i--] = '\0';
/* reverse the output in-place */
for (size_t j = 0; j < i; --i, ++j) {
char c = buf[i];
buf[i] = buf[j];
buf[j] = c;
}
}
return digits;
}
#include <stdio.h>
#include <string.h>
int test_itoa(const char *file, int line,
int n, const char *expected)
{
char buf[20];
int result = 0;
size_t actual = itoa(n, 0, 0);
if (actual != strlen(expected)) {
fprintf(stderr, "%s:%d: error: itoa(%d) returned %zu instead of %zu\n",
file, line,
n, actual, strlen(expected));
result = 1;
}
actual = itoa(n, buf, sizeof buf);
if (actual != strlen(expected)) {
fprintf(stderr, "%s:%d: error: itoa(%d) returned %zu instead of %zu\n",
file, line,
n, actual, strlen(expected));
result = 1;
}
if (strcmp(buf, expected)) {
fprintf(stderr, "%s:%d: error: itoa(%d) produced %s instead of %s\n",
file, line,
n, buf, expected);
result = 1;
}
return result;
}
#define TEST_ITOA(n, s) test_itoa(__FILE__, __LINE__, n, s)
int main(void)
{
return TEST_ITOA(0, "0")
| TEST_ITOA(1, "1")
| TEST_ITOA(10, "10")
| TEST_ITOA(12345, "12345")
| TEST_ITOA(-1, "-1")
| TEST_ITOA(-10, "-10")
| TEST_ITOA(-12345, "-12345")
;
}
n == 0
(since you will get the correct output with the rest of the code).get_digits
(which should be completely eliminated) could possibly be improved a bit with some nested conditionals (if (num < 100) return num < 10 ? 1 : 2;
), although smaller numbers will be more common that larger ones. \$\endgroup\$i >= 1
, therefore the special case forn == 0
is indeed needed. \$\endgroup\$i = n; do { ... } while ((i /= 10) >= 1);
to avoid the overhead of a probably rarely needed special case. \$\endgroup\$