I need to print signed 64-bit numbers in decimal form. Program runs in freestanding environment (no C library available, libgcc may be unavailable too, or may not work correctly). So I can't use printf(3) function.
I'am trying to write function, which will be able to convert number from binary form (int64_t) to ascii. Simple solution is to divide number by 10, and use remainder as next digit (from left to right, iteratively).
Unfortunately, processor doesn't have 64 bit division, and I can't rely on libgcc in that (__udivdi3 function). So I invented other way to perform binary->decimal conversion, by using binary coded decimal (BCD) numbers. First I convert binary number to BCD form (bit by bit, need 64 iterations, and need to sum BCD numbers), then convert BCD to ASCII.
Source code is following:
/*static*/ void _print_dec(intmax_t num)
{
char *p;
char buf[24 /* > log10(2^64) */];
uint_fast8_t r;
uint64_t n = num < 0 ? -num : +num;
uint_fast16_t result_h = 0, digit_h = 0;
uint64_t result = 0, digit = 1;
while (n != 0)
{
/* sum bcd numbers: r = a + b, where r should be L-value */
#define BCD_ADD(type, r, a, b) do { \
type t1 = a + (type)(0x6666666666666666 & ((type)-1 >> 4)), t2 = t1 ^ b; \
t1 += b, t2 = ~(t1 ^ t2) & (type)0x1111111111111110; r = t1 - (t2>>2 | t2>>3); \
} while (0);
/* this macro computes rh:rl = ah:al + bh:bl (sum of compound BCD number), rh:rl should be L-value */
#define BCD_ADD_COMPOUND(type_h, type, rh, rl, ah, al, bh, bl) do { \
uint_fast8_t c = 0; \
type t1 = al, t2 = bl; \
BCD_ADD(type, rl, al, bl); \
c = rl >> (sizeof(type)*CHAR_BIT-4); \
BCD_ADD(type_h, rh, ah, bh); \
if (c >= 10 || (rl < t1 && rl < t2)) { \
rl = (rl & ((type)-1 >> 4)) | ((c - 10ULL) << (sizeof(type)*CHAR_BIT-4)); \
BCD_ADD(type_h, rh, rh, 1); \
} \
} while (0)
if (n & 1)
BCD_ADD_COMPOUND(uint_fast16_t, uint64_t, result_h, result, result_h, result, digit_h, digit);
BCD_ADD_COMPOUND(uint_fast16_t, uint64_t, digit_h, digit, digit_h, digit, digit_h, digit);
n >>= 1;
}
p = &buf[sizeof(buf) - 1];
*p = 0;
do {
r = result & 0x0f;
*--p = r + '0';
result = (result >> 4) | ((uint64_t)(result_h & 0x0f) << (sizeof(uint64_t)*CHAR_BIT-4));
result_h = result_h >> 4;
} while (result_h != 0 || result != 0);
if (num < 0) *--p = '-';
print_cstr(p);
}
#define print_dec(num) DECL_FUNC(_print_dec, num)
I hope, that somebody suggests me some improvements...