I'm writing a library to register the b
, B
conversion specifiers and make them work the closest possible to o
, u
, x
, X
.
Requirements:
- All "flag characters" should behave as in
x
,X
(or as close as possible, while being meaningful). - "Field width" and "precision" should behave as in
x
,X
(or as close as possible, while being meaningful). - All "length modifiers" that are valid on
o
,u
,x
,X
should be valid. - They should work on any
printf
family function (such asfprintf
,snprintf
, ...). - There are some flags that override others in the case of
o
,u
,x
,X
. Those behaviours should be kept the same.
Basically, they should work so that a user can predict the output from reading the man printf
page.
I decided that the '
flag character, which normally should group the output in thousands, would be more meaningful here if it grouped nibbles (half bytes), and instead of using the locale for thousands separator, it should use _
. But I'm open to improvements here.
Here goes the code:
/* 2019 - Alejandro Colomar Andrés */
/******************************************************************************
******* headers **************************************************************
******************************************************************************/
#include "libalx/base/stdio/printf.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <printf.h>
/******************************************************************************
******* macros ***************************************************************
******************************************************************************/
/******************************************************************************
******* enums ****************************************************************
******************************************************************************/
/******************************************************************************
******* structs / unions *****************************************************
******************************************************************************/
/******************************************************************************
******* variables ************************************************************
******************************************************************************/
/******************************************************************************
******* static functions (prototypes) ****************************************
******************************************************************************/
static int printf_output_b (FILE *stream,
const struct printf_info *info,
const void *const *args);
static int printf_arginf_sz_b (const struct printf_info *info,
size_t n, int *argtypes, int *size);
/******************************************************************************
******* global functions *****************************************************
******************************************************************************/
int alx_printf_init (void)
{
if (register_printf_specifier('b', printf_output_b, printf_arginf_sz_b))
return -1;
if (register_printf_specifier('B', printf_output_b, printf_arginf_sz_b))
return -1;
return 0;
}
/******************************************************************************
******* static functions (definitions) ***************************************
******************************************************************************/
static int printf_output_b (FILE *stream,
const struct printf_info *info,
const void *const *args)
{
uintmax_t val;
uintmax_t utmp;
bool bin[sizeof(val) * 8];
int min_len;
int bin_len;
int pad_len;
int tmp;
char pad_ch;
size_t len;
len = 0;
if (info->is_long_double)
val = *(unsigned long long *)args[0];
else if (info->is_long)
val = *(unsigned long *)args[0];
else if (info->is_char)
val = *(unsigned char *)args[0];
else if (info->is_short)
val = *(unsigned short *)args[0];
else
val = *(unsigned *)args[0];
/* Binary representation */
memset(bin, 0, sizeof(bin));
utmp = val;
for (min_len = 0; utmp; min_len++) {
if (utmp % 2)
bin[min_len] = 1;
utmp >>= 1;
}
if (info->prec > min_len)
bin_len = info->prec;
else
bin_len = min_len;
/* Padding char */
if ((info->prec != -1) || (info->pad == ' ') || info->left)
pad_ch = ' ';
else
pad_ch = '0';
/* Padding length */
if (pad_ch == ' ') {
pad_len = info->width - bin_len;
if (info->alt)
pad_len -= 2;
if (info->group)
pad_len -= (bin_len - 1) / 4;
if (pad_len < 0)
pad_len = 0;
}
/* Padding with ' ' (right aligned) */
if ((pad_ch == ' ') && !info->left) {
for (int i = pad_len; i; i--) {
if (fputc(' ', stream) == EOF)
return EOF;
}
len += pad_len;
}
/* "0b"/"0B" prefix */
if (info->alt && val) {
if (fputc('0', stream) == EOF)
return EOF;
if (fputc(info->spec, stream) == EOF)
return EOF;
len += 2;
}
/* Padding with '0' */
if (pad_ch == '0') {
tmp = info->width - (info->alt * 2);
if (info->group)
tmp -= (tmp - min_len + 3) / 4;
for (int i = tmp - 1; i > min_len - 1; i--) {
if (fputc('0', stream) == EOF)
return EOF;
if (info->group && !(i % 4) && i) {
if (fputc('_', stream) == EOF)
return EOF;
len++;
}
}
len += tmp - min_len;
}
/* Print leading zeros to fill precission */
for (int i = bin_len - 1; i > min_len - 1; i--) {
if (fputc('0', stream) == EOF)
return EOF;
if (info->group && !(i % 4) && i) {
if (fputc('_', stream) == EOF)
return EOF;
len++;
}
}
len += bin_len - min_len;
/* Print number */
for (int i = min_len - 1; i; i--) {
if (fputc('0' + bin[i], stream) == EOF)
return EOF;
if (info->group && !(i % 4) && i) {
if (fputc('_', stream) == EOF)
return EOF;
len++;
}
}
if (fputc('0' + bin[0], stream) == EOF)
return EOF;
len += min_len;
/* Padding with ' ' (left aligned) */
if (info->left) {
for (int i = pad_len; i; i--) {
if (fputc(' ', stream) == EOF)
return EOF;
}
len += pad_len;
}
return len;
}
static int printf_arginf_sz_b (const struct printf_info *info,
size_t n, int *argtypes, int *size)
{
if (n > 0)
argtypes[0] = PA_INT;
return 1;
}
/******************************************************************************
******* end of file **********************************************************
******************************************************************************/
It seems that it works as expected (at least on my tests), but I feel like the code is very heavy and long and could be simplified.
Here is what I've tested:
#include "libalx/base/stdio/printf.h"
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
int main()
{
alx_printf_init();
printf("....----....----....----....----\n");
printf("%llb;\n", 0x5Ellu);
printf("%lB;\n", 0x5Elu);
printf("%b;\n", 0x5Eu);
printf("%hB;\n", 0x5Eu);
printf("%hhb;\n", 0x5Eu);
printf("%jb;\n", (uintmax_t)0x5E);
printf("%zb;\n", (size_t)0x5E);
printf("....----....----....----....----\n");
printf("%#b;\n", 0x5Eu);
printf("%#B;\n", 0x5Eu);
printf("....----....----....----....----\n");
printf("%10b;\n", 0x5Eu);
printf("%010b;\n", 0x5Eu);
printf("%.10b;\n", 0x5Eu);
printf("....----....----....----....----\n");
printf("%-10B;\n", 0x5Eu);
printf("....----....----....----....----\n");
printf("%'B;\n", 0x5Eu);
printf("....----....----....----....----\n");
printf("....----....----....----....----\n");
printf("%#16.12b;\n", 0xAB);
printf("%-#'20.12b;\n", 0xAB);
printf("%#'020B;\n", 0xAB);
printf("....----....----....----....----\n");
printf("%#020B;\n", 0xAB);
printf("%'020B;\n", 0xAB);
printf("%020B;\n", 0xAB);
printf("....----....----....----....----\n");
printf("%#021B;\n", 0xAB);
printf("%'021B;\n", 0xAB);
printf("%021B;\n", 0xAB);
printf("....----....----....----....----\n");
printf("%#022B;\n", 0xAB);
printf("%'022B;\n", 0xAB);
printf("%022B;\n", 0xAB);
printf("....----....----....----....----\n");
printf("%#023B;\n", 0xAB);
printf("%'023B;\n", 0xAB);
printf("%023B;\n", 0xAB);
printf("....----....----....----....----\n");
printf("%-#'19.11b;\n", 0xAB);
printf("%#'019B;\n", 0xAB);
printf("%#019B;\n", 0xAB);
printf("....----....----....----....----\n");
printf("%'019B;\n", 0xAB);
printf("%019B;\n", 0xAB);
printf("%#016b;\n", 0xAB);
printf("....----....----....----....----\n");
return 0;
}
And its output:
....----....----....----....----
1011110;
1011110;
1011110;
1011110;
1011110;
1011110;
1011110;
....----....----....----....----
0b1011110;
0B1011110;
....----....----....----....----
1011110;
0001011110;
0001011110;
....----....----....----....----
1011110 ;
....----....----....----....----
101_1110;
....----....----....----....----
....----....----....----....----
0b000010101011;
0b0000_1010_1011 ;
0B000_0000_1010_1011;
....----....----....----....----
0B000000000010101011;
0_0000_0000_1010_1011;
00000000000010101011;
....----....----....----....----
0B0000000000010101011;
0_0000_0000_1010_1011;
000000000000010101011;
....----....----....----....----
0B00000000000010101011;
00_0000_0000_1010_1011;
0000000000000010101011;
....----....----....----....----
0B000000000000010101011;
000_0000_0000_1010_1011;
00000000000000010101011;
....----....----....----....----
0b000_1010_1011 ;
0B00_0000_1010_1011;
0B00000000010101011;
....----....----....----....----
0000_0000_1010_1011;
0000000000010101011;
0b00000010101011;
....----....----....----....----
I understood the use of printf_output_b
, but I still don't know very well the use of printf_arginf_sz_b
, and if I'm missing anything.
Is there anything you miss here, or you think could be improved?
Also, let's say that uintmax_t
or size_t
are wider than unsigned long long
. How would I handle that? I don't receive any information about those in the struct
, AFAIK.
EDIT: Add the struct and enum definitions from <printf.h>
struct printf_info{
int prec; /* Precision. */
int width; /* Width. */
wchar_t spec; /* Format letter. */
unsigned int is_long_double:1;/* L flag. */
unsigned int is_short:1; /* h flag. */
unsigned int is_long:1; /* l flag. */
unsigned int alt:1; /* # flag. */
unsigned int space:1; /* Space flag. */
unsigned int left:1; /* - flag. */
unsigned int showsign:1; /* + flag. */
unsigned int group:1; /* ' flag. */
unsigned int extra:1; /* For special use. */
unsigned int is_char:1; /* hh flag. */
unsigned int wide:1; /* Nonzero for wide character streams. */
unsigned int i18n:1; /* I flag. */
unsigned int is_binary128:1; /* Floating-point argument is ABI-compatible with IEC 60559 binary128. */
unsigned int __pad:3; /* Unused so far. */
unsigned short int user; /* Bits for user-installed modifiers. */
wchar_t pad; /* Padding character. */
};
enum{ /* C type: */
PA_INT, /* int */
PA_CHAR, /* int, cast to char */
PA_WCHAR, /* wide char */
PA_STRING, /* const char *, a '\0'-terminated string */
PA_WSTRING, /* const wchar_t *, wide character string */
PA_POINTER, /* void * */
PA_FLOAT, /* float */
PA_DOUBLE, /* double */
PA_LAST
};
printf_arginf_sz_b()
does as well as the size of uintmax_t and size_t? \$\endgroup\$"%jb"
(uint_max
) for example). Also, obviously, if there is any UB, I would like to know. \$\endgroup\$struct printf_info
and PA_INT. \$\endgroup\$<printf.h>
from glibc (github.com/lattera/glibc/blob/master/stdio-common/printf.h). I edited the question to add them. \$\endgroup\$