printing bits ... the right way

I

ImpalerCore

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdint.h>
#include <ctype.h>

/*!
* \brief Return the number of bits in type \c TYPE.
* \param TYPE The type.
* \return The size of the type in bits.
*
* The \c TYPE argument can also be a variable declared with a given
* type. The sizeof operator will determine the variable's size in
* bits from its underlying type.
*/
#define C_TYPE_BITS(TYPE) (sizeof(TYPE) * 8)

/*!
* \brief Converts a formatted variable argument list of integers into
* binary strings storing up to \c n-1 characters of the result
* in the buffer \c str.
* \param str The array of characters where the resulting string is to
be
* stored.
* \param n The size of the string \c str.
* \param format A custom format string specific to converting
integers
* to binary strings.
* \param args The parameters to substitute into the format string.
* \return The number of characters stored in \c str, not including
the
* terminating nul character. If the output was truncated
because
* \c c_bit_vsnprintf will not write more than \c n bytes, the
* value returned is the number of characters that would have
been
* written to the string if enough space had been available.
*
* Note that a return value of \c n or more means that the output was
* truncated. If an output error occurs, a negative value is
returned.
*/
int c_bit_vsnprintf( char* str, size_t n, const char* format, va_list
args );

/*!
* \brief Converts a formatted variable argument list of integers into
* binary strings storing up to \c n-1 characters of the result
* in the buffer \c str.
* \param str The array of characters where the resulting string is to
be
* stored.
* \param n The size of the string \c str.
* \param format A custom format string specific to converting
integers
* to binary strings.
* \param ... The parameters to substitute into the format string.
* \return The number of characters stored in \c str, not including
the
* terminating nul character. If the output was truncated
because
* \c c_bit_vsnprintf will not write more than \c n bytes, the
* value returned is the number of characters that would have
been
* written to the string if enough space had been available.
*
* The \c c_bit_snprintf function will write at most \c n-1 of the
* characters into the output string (reserving a single character for
* the terminating nul character). If the return value is greater
than
* or equal to the size of the \c str array, the string was truncated
and
* some of the characters were discarded. If \c n is zero, nothing is
* written and \c str may be a \c NULL pointer.
*
* The format string is composed of conversion specifiers that will
* render an integer type into a binary string. The format tags
follow
* the prototype below, where the specifier is one of the values in
the
* table.
*
* \code
* %[spacing]specifier
* \endcode
*
* Specifier - Output
* b - Binary string of an 8-bit byte
* w - Binary string of a 16-bit word
* dw - Binary string of a 32-bit double word
* qw - Binary string of a 64-bit quad word
*
* The spacing format is used to insert spaces at uniform or custom
* field widths. The bit field spacing starts from the most-
significant
* bit and works towards the least-significant bit. The uniform
spacing
* format uses a single positive number placed before the conversion
* specifier.
*
* The custom field width format uses numbers delimited by '.'
characters
* to define the number of bits to place in a field, starting from the
* most-significant bit. If the cumulative size of the field widths
is
* less than the number of bits in the integer, the remaining bits are
* grouped into a single field.
*
* Note that a return value of \c n or more means that the output was
* truncated. If an output error occurs, a negative value is
returned.
*/
int c_bit_snprintf( char* str, size_t n, const char* format, ... );

/*!
* \brief Converts a formatted variable argument list of integers into
* binary strings writing the result to a file stream.
* \param stream A file stream.
* \param format A custom format string specific to converting
integers
* to binary strings.
* \param args The parameters to substitute into the format string.
* \return The number of characters written to file. If an output
error
* occurs, a negative value is returned.
*/
int c_bit_vfprintf( FILE* stream, const char* format, va_list args );

/*!
* \brief Converts a formatted variable argument list of integers into
* binary strings writing the result to a file stream.
* \param stream A file stream.
* \param format A custom format string specific to converting
integers
* to binary strings.
* \param ... The parameters to substitute into the format string.
* \return The number of characters written to file. If an output
error
* occurs, a negative value is returned.
*
* The \c c_bit_fprintf function is not intended to be a replacement
* for \c fprintf with additional conversion specifiers. Instead,
* it is intended for easily writing binary strings to a stream for
* debugging or logging purposes.
*
* The format string is composed of conversion specifiers that will
* render an integer type into a binary string. The format tags
follow
* the prototype below, where the specifier is one of the values in
the
* table.
*
* \code
* %[spacing]specifier
* \endcode
*
* Specifier - Output
* b - Binary string of an 8-bit byte
* w - Binary string of a 16-bit word
* dw - Binary string of a 32-bit double word
* qw - Binary string of a 64-bit quad word
*
* The spacing format is used to insert spaces at uniform or custom
* field widths. The bit field spacing starts from the most-
significant
* bit and works towards the least-significant bit. The uniform
spacing
* format uses a single positive number placed before the conversion
* specifier.
*
* The custom field width format uses numbers delimited by '.'
characters
* to define the number of bits to place in a field, starting from the
* most-significant bit. If the cumulative size of the field widths
is
* less than the number of bits in the integer, the remaining bits are
* grouped into a single field.
*/
int c_bit_fprintf( FILE* stream, const char* format, ... );

/*!
* \brief Converts a formatted variable argument list of integers into
* binary strings printing the result to the standard output.
* \param format A custom format string specific to converting
integers
* to binary strings.
* \param args The parameters to substitute into the format string.
* \return The number of characters printed to the standard output.
* If an output error occurs, a negative value is returned.
*/
int c_bit_vprintf( const char* format, va_list args );

/*!
* \brief Converts a formatted variable argument list of integers into
* binary strings printing the result to the standard output.
* \param format A custom format string specific to converting
integers
* to binary strings.
* \param ... The parameters to substitute into the format string.
* \return The number of characters printed to the standard output.
* If an output error occurs, a negative value is returned.
*
* The \c c_bit_printf function is not intended to be a replacement
* for \c printf with additional conversion specifiers. Instead,
* it is intended for easily printing binary strings for debugging
* purposes.
*
* The format string is composed of conversion specifiers that will
* render an integer type into a binary string. The format tags
follow
* the prototype below, where the specifier is one of the values in
the
* table.
*
* \code
* %[spacing]specifier
* \endcode
*
* Specifier - Output
* b - Binary string of an 8-bit byte
* w - Binary string of a 16-bit word
* dw - Binary string of a 32-bit double word
* qw - Binary string of a 64-bit quad word
*
* The spacing format is used to insert spaces at uniform or custom
* field widths. The bit field spacing starts from the most-
significant
* bit and works towards the least-significant bit. The uniform
spacing
* format uses a single positive number placed before the conversion
* specifier.
*
* The custom field width format uses numbers delimited by '.'
characters
* to define the number of bits to place in a field, starting from the
* most-significant bit. If the cumulative size of the field widths
is
* less than the number of bits in the integer, the remaining bits are
* grouped into a single field.
*/
int c_bit_printf( const char* format, ... );

int c_bit_vsnprintf( char* str, size_t n, const char* format, va_list
args )
{
size_t length = 0;
const char* p = format;
char* q = str;

size_t bit_spacing_width;
unsigned int base10_number;

uint32_t arg_u;
uint8_t arg_u8;
uint16_t arg_u16;
uint32_t arg_u32;
uint64_t arg_u64;
char conversion_specifier;

uint8_t field_spec[128];
int field_count;

int index;
int fsi;
char bc;

#define EMIT_C(c) \
if ( length < n ) { \
*q++ = c; \
} \
++length;

#define EMIT_BINARY(arg_type) \
index = C_TYPE_BITS( arg_type ) - 1; \
while ( index >= 0 ) { \
bc = '0' + (( arg_type >> index ) & 1); \
EMIT_C( bc ); \
--index; \
}

#define EMIT_BINARY_UNIFORM_SPACING(arg_type) \
fsi = bit_spacing_width; \
index = C_TYPE_BITS( arg_type ) - 1; \
while ( index >= 0 ) { \
bc = '0' + (( arg_type >> index ) & 1); \
EMIT_C( bc ); \
--fsi; \
if ( fsi == 0 ) { \
if ( index != 0 ) { \
EMIT_C( ' ' ); \
} \
fsi = bit_spacing_width; \
} \
--index; \
}

#define EMIT_BINARY_FIELD_WIDTH(arg_type) \
fsi = 0; \
index = C_TYPE_BITS( arg_type ) - 1; \
while ( index >= 0 ) { \
bc = '0' + (( arg_type >> index ) & 1); \
EMIT_C( bc ); \
if ( fsi < field_count ) { \
--field_spec[fsi]; \
if ( field_spec[fsi] == 0 ) { \
++fsi; \
if ( index != 0 ) { \
EMIT_C( ' ' ); \
} \
} \
} \
--index; \
}

if ( !p ) {
p = "";
}

while ( *p != '\0' )
{
if ( *p != '%' )
{
EMIT_C( *p );
++p;
}
else
{
++p;
base10_number = 0;
bit_spacing_width = 0;
field_count = 0;

/* Look for bit spacing width. */
if ( isdigit( (int)(*p) ) )
{
base10_number = *p++ - '0';

while ( isdigit( (int)(*p) ) ) {
base10_number = 10 * base10_number + (unsigned int)( *p++ -
'0' );
}
bit_spacing_width = base10_number;
}

if ( *p && *p == '.' )
{
++p;

/*
* This is a variable bit field sequence, so if a number was
read,
* it is actually the first bit field width.
*/
if ( bit_spacing_width )
{
field_spec[field_count] = bit_spacing_width;
bit_spacing_width = 0;
++field_count;
}

while ( *p != '\0' )
{
if ( *p == '.' ) {
++p;
}
else if ( isdigit( (int)(*p) ) )
{
base10_number = *p++ - '0';

while ( isdigit( (int)(*p) ) ) {
base10_number = 10 * base10_number + (unsigned int)( *p+
+ - '0' );
}

/* Zero length fields are ignored. */
if ( base10_number && field_count <
(int)sizeof( field_spec ) )
{
field_spec[field_count] = base10_number;
++field_count;
}
}
else {
break;
}
}
}

conversion_specifier = *p;
switch ( conversion_specifier )
{
case '%':
EMIT_C( *p );
++p;
break;

case 'b':
arg_u = va_arg( args, uint32_t );
arg_u8 = (uint8_t)arg_u;
++p;

if ( field_count ) {
EMIT_BINARY_FIELD_WIDTH( arg_u8 );
}
else if ( bit_spacing_width ) {
EMIT_BINARY_UNIFORM_SPACING( arg_u8 );
}
else {
EMIT_BINARY( arg_u8 );
}
break;

case 'w':
arg_u = va_arg( args, uint32_t );
arg_u16 = (uint16_t)arg_u;
++p;

if ( field_count ) {
EMIT_BINARY_FIELD_WIDTH( arg_u16 );
}
else if ( bit_spacing_width ) {
EMIT_BINARY_UNIFORM_SPACING( arg_u16 );
}
else {
EMIT_BINARY( arg_u16 );
}
break;

/* Looking for "dw" format specifier. */
case 'd':
++p;
if ( *p && *p == 'w' )
{
arg_u = va_arg( args, uint32_t );
arg_u32 = (uint32_t)arg_u;
++p;

if ( field_count ) {
EMIT_BINARY_FIELD_WIDTH( arg_u32 );
}
else if ( bit_spacing_width ) {
EMIT_BINARY_UNIFORM_SPACING( arg_u32 );
}
else {
EMIT_BINARY( arg_u32 );
}
}
break;

/* Looking for "qw" format specifier. */
case 'q':
++p;
if ( *p && *p == 'w' )
{
arg_u64 = va_arg( args, uint64_t );
++p;

if ( field_count ) {
EMIT_BINARY_FIELD_WIDTH( arg_u64 );
}
else if ( bit_spacing_width ) {
EMIT_BINARY_UNIFORM_SPACING( arg_u64 );
}
else {
EMIT_BINARY( arg_u64 );
}
}
break;

default:
break;
}
}
}

/*
* The string should be nul terminated, even if it overwrites the
last
* character in the buffer.
*/
if ( n > 0 ) {
str[ length <= n-1 ? length : n-1 ] = '\0';
}

return (int)length;
}

int c_bit_snprintf( char* str, size_t n, const char* format, ... )
{
va_list args;
int result;

va_start( args, format );
result = c_bit_vsnprintf( str, n, format, args );
va_end( args );

return result;
}

int c_bit_vfprintf( FILE* stream, const char* format, va_list args )
{
int result;
char* buf = NULL;
size_t length = 0;

length = c_bit_vsnprintf( NULL, 0, format, args );
buf = malloc( length + 1 );

if ( buf )
{
length = c_bit_vsnprintf( buf, length + 1, format, args );
fwrite( buf, 1, length, stream );

free( buf );

result = (int)length;
}
else {
result = -1;
}

return result;
}

int c_bit_fprintf( FILE* stream, const char* format, ... )
{
va_list args;
int result;

va_start( args, format );
result = c_bit_vfprintf( stream, format, args );
va_end( args );

return result;
}

int c_bit_vprintf( const char* format, va_list args )
{
return c_bit_vfprintf( stdout, format, args );
}

int c_bit_printf( const char* format, ... )
{
va_list args;
int result;

va_start( args, format );
result = c_bit_vprintf( format, args );
va_end( args );

return result;
}

int main( void )
{
uint8_t u8 = 0xFE;
uint16_t u16 = 0xDCBA;
uint32_t u32 = 0x76543210;
uint64_t u64 = UINT64_C( 0xFEDCBA9876543210 );

char bstr[64];
int len;

len = c_bit_snprintf( bstr, sizeof( bstr ), "%1b", u8 );
printf( " u8 length [expected,actual] = [%d,%lu]\n", len,
strlen( bstr ) );
printf( " bstr [%s]\n", bstr );
printf( "\n" );

len = c_bit_snprintf( bstr, sizeof( bstr ), "%w", u16 );
printf( "u16 length [expected,actual] = [%d,%lu]\n", len,
strlen( bstr ) );
printf( " bstr [%s]\n", bstr );
printf( "\n" );

len = c_bit_snprintf( bstr, sizeof( bstr ), "%4dw", u32 );
printf( "u32 length [expected,actual] = [%d,%lu]\n", len,
strlen( bstr ) );
printf( " bstr [%s]\n", bstr );
printf( "\n" );

len = c_bit_snprintf( bstr, sizeof( bstr ), "%8qw", u64 );
printf( "u64 length [expected,actual] = [%d,%lu]\n", len,
strlen( bstr ) );
printf( " bstr [%s]\n", bstr );
printf( "\n" );

len = c_bit_snprintf( bstr, sizeof( bstr ), "%4.2.1.1b", u8 );
printf( " u8 length [expected,actual] = [%d,%lu]\n", len,
strlen( bstr ) );
printf( " bstr [%s]\n", bstr );
printf( "\n" );

len = c_bit_snprintf( bstr, sizeof( bstr ), "%4.w", u16 );
printf( "u16 length [expected,actual] = [%d,%lu]\n", len,
strlen( bstr ) );
printf( " bstr [%s]\n", bstr );
printf( "\n" );

len = c_bit_snprintf( bstr, sizeof( bstr ), "%14.7.1.10dw", u32 );
printf( "u32 length [expected,actual] = [%d,%lu]\n", len,
strlen( bstr ) );
printf( " bstr [%s]\n", bstr );
printf( "\n" );

len = c_bit_snprintf( bstr, sizeof( bstr ), "%1.2.3.4.5.6.7.8.qw",
u64 );
printf( "u64 length [expected,actual] = [%d,%lu]\n", len,
strlen( bstr ) );
printf( " bstr [%s]\n", bstr );

return EXIT_SUCCESS;
}

<todo>
(Insert some comment about the amount of time I spent to code up the
solution and brag about how fast I did it and how everyone else's
solution is inferior)
(Insert disclaimer that I'm absolved of any responsibility for fixing
or maintaining this code for anyone who decides to use this code to do
anything remotely useful)
(Insert comment that using macros to duplicate type-specific code
generation is bad in general but works just fine in this case, yadda
yadda)
(Insert some poetic construction to elucidate the clc regulars in the
errors of their limited commercial mindsets, particularly those that
haven't taken proper college undergraduate courses, ... well okay I'll
do it)
</todo>

For once I did something clever
When no one else seemed to endeavor
Put up bits to display
With a code knife to filet
Now my troubles I can finally sever

Best regards to everyone that makes clc what it is; the good, the bad,
and the ugly.
John D.
 
I

Ike Naar

/*!
* \brief Return the number of bits in type \c TYPE.
* \param TYPE The type.
* \return The size of the type in bits.
*
* The \c TYPE argument can also be a variable declared with a given
* type. The sizeof operator will determine the variable's size in
* bits from its underlying type.

For that matter, TYPE can be any expression (it need not be a variable).
Example:

double **p;
C_TYPE_BITS(*p[42] + *p[43]); /* equivalent to C_TYPE_BITS(double) */
*/
#define C_TYPE_BITS(TYPE) (sizeof(TYPE) * 8)

Slightly more portable:

#include <limits.h>
#define C_TYPE_BITS(TYPE) (sizeof(TYPE) * CHAR_BIT)
 
I

ImpalerCore

ImpalerCore   said:
/*!
* \brief Return the number of bits in type \c TYPE.
* \param TYPE The type.
* \return The size of the type in bits.
*
* The \c TYPE argument can also be a variable declared with a given
* type.  The sizeof operator will determine the variable's size in
* bits from its underlying type.

For that matter, TYPE can be any expression (it need not be a variable).
Example:

    double **p;
    C_TYPE_BITS(*p[42] + *p[43]); /* equivalent to C_TYPE_BITS(double) */
*/
#define C_TYPE_BITS(TYPE) (sizeof(TYPE) * 8)

Slightly more portable:

    #include <limits.h>
    #define C_TYPE_BITS(TYPE) (sizeof(TYPE) * CHAR_BIT)

Thanks for the tidbit. I never think about systems that don't have 8
bit chars since I've never used one.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,482
Members
44,901
Latest member
Noble71S45

Latest Threads

Top