In reality, we often express long binary numbers in the form of 0000 1111 0011 1100
to make reading easier. And very often, especially in dealing with chip-chip communications in embedded systems (like I2C), we have to perform bitwise logical operations (usually bitwise OR) to abstract informations for decisions.
Yet as far as I know, in the C standard, no spacing or any form of separation between digits is allowed, which makes it more difficult to read the long binary numbers. Using hexadecimal number is somehow worse since it masks the binary number.
Is there a conforming way to express long binary numbers with separators (such as 0b0000 1111 0011 1100
) in C?
In reality, we often express long binary numbers in the form of 0000 1111 0011 1100
to make reading easier. And very often, especially in dealing with chip-chip communications in embedded systems (like I2C), we have to perform bitwise logical operations (usually bitwise OR) to abstract informations for decisions.
Yet as far as I know, in the C standard, no spacing or any form of separation between digits is allowed, which makes it more difficult to read the long binary numbers. Using hexadecimal number is somehow worse since it masks the binary number.
Is there a conforming way to express long binary numbers with separators (such as 0b0000 1111 0011 1100
) in C?
4 Answers
Reset to default 7Yes, with the newly released C standard (C23), you can use '
as a digit separator:
uint16_t x = 0b0000'1111'0011'1100;
Digit separators are ignored when determining the value of the constant.
Using a macro you can compose the binary number. You can handle octal numbers specially. Additional casts can be used to handle more bits then unsigned int
type. Macro can be overloaded on number of arguments.
The following code allows to create the number with BINARY(0000,1111,0011,1100)
.
#include <stdint.h>
#include <stdio.h>
// creates a binary 4 digit number from 1010 or 0101 or similar
#define BINARY_1(a) ( \
a== 0? 0u:\
a== 1? 1u:\
a== 10? 2u:\
a== 11? 3u:\
a== 100? 4u:\
a== 101? 5u:\
a== 110? 6u:\
a== 111? 7u:\
a==1000? 8u:\
a==1001? 9u:\
a==1010?10u:\
a==1011?11u:\
a==1100?12u:\
a==1101?13u:\
a==1110?14u:\
a==1111?15u:\
a==0001? 1u:\
a==0010? 2u:\
a==0011? 3u:\
a==0100? 4u:\
a==0101? 5u:\
a==0110? 6u:\
a==0111? 7u:\
-1)
#define BINARY_2(a,...) ((uint_least8_t)BINARY_1(a)<<4|BINARY_1(__VA_ARGS__))
#define BINARY_3(a,...) ((uint_least16_t)BINARY_1(a)<<8|BINARY_2(__VA_ARGS__))
#define BINARY_4(a,...) ((uint_least16_t)BINARY_1(a)<<12|BINARY_3(__VA_ARGS__))
#define BINARY_5(a,...) ((uint_least32_t)BINARY_1(a)<<16|BINARY_4(__VA_ARGS__))
#define BINARY_6(a,...) ((uint_least32_t)BINARY_1(a)<<20|BINARY_5(__VA_ARGS__))
#define BINARY_7(a,...) ((uint_least32_t)BINARY_1(a)<<24|BINARY_6(__VA_ARGS__))
#define BINARY_8(a,...) ((uint_least32_t)BINARY_1(a)<<28|BINARY_7(__VA_ARGS__))
#define BINARY_9(a,...) ((uint_least64_t)BINARY_1(a)<<32|BINARY_8(__VA_ARGS__))
#define BINARY_10(a,...) ((uint_least64_t)BINARY_1(a)<<36|BINARY_9(__VA_ARGS__))
#define BINARY_11(a,...) ((uint_least64_t)BINARY_1(a)<<40|BINARY_10(__VA_ARGS__))
#define BINARY_12(a,...) ((uint_least64_t)BINARY_1(a)<<44|BINARY_11(__VA_ARGS__))
#define BINARY_13(a,...) ((uint_least64_t)BINARY_1(a)<<48|BINARY_12(__VA_ARGS__))
#define BINARY_14(a,...) ((uint_least64_t)BINARY_1(a)<<52|BINARY_13(__VA_ARGS__))
#define BINARY_15(a,...) ((uint_least64_t)BINARY_1(a)<<56|BINARY_14(__VA_ARGS__))
#define BINARY_16(a,...) ((uint_least64_t)BINARY_1(a)<<60|BINARY_15(__VA_ARGS__))
#define BINARY_17(a,...) ((__uint128_t)BINARY_1(a)<<64|BINARY_16(__VA_ARGS__))
#define BINARY_18(a,...) ((__uint128_t)BINARY_1(a)<<68|BINARY_17(__VA_ARGS__))
#define BINARY_19(a,...) ((__uint128_t)BINARY_1(a)<<72|BINARY_18(__VA_ARGS__))
#define BINARY_20(a,...) ((__uint128_t)BINARY_1(a)<<76|BINARY_19(__VA_ARGS__))
#define BINARY_21(a,...) ((__uint128_t)BINARY_1(a)<<80|BINARY_20(__VA_ARGS__))
#define BINARY_22(a,...) ((__uint128_t)BINARY_1(a)<<84|BINARY_21(__VA_ARGS__))
#define BINARY_23(a,...) ((__uint128_t)BINARY_1(a)<<88|BINARY_22(__VA_ARGS__))
#define BINARY_24(a,...) ((__uint128_t)BINARY_1(a)<<92|BINARY_23(__VA_ARGS__))
#define BINARY_25(a,...) ((__uint128_t)BINARY_1(a)<<96|BINARY_24(__VA_ARGS__))
#define BINARY_26(a,...) ((__uint128_t)BINARY_1(a)<<100|BINARY_25(__VA_ARGS__))
#define BINARY_27(a,...) ((__uint128_t)BINARY_1(a)<<104|BINARY_26(__VA_ARGS__))
#define BINARY_28(a,...) ((__uint128_t)BINARY_1(a)<<108|BINARY_27(__VA_ARGS__))
#define BINARY_29(a,...) ((__uint128_t)BINARY_1(a)<<112|BINARY_28(__VA_ARGS__))
#define BINARY_30(a,...) ((__uint128_t)BINARY_1(a)<<116|BINARY_29(__VA_ARGS__))
#define BINARY_31(a,...) ((__uint128_t)BINARY_1(a)<<120|BINARY_30(__VA_ARGS__))
#define BINARY_32(a,...) ((__uint128_t)BINARY_1(a)<<124|BINARY_31(__VA_ARGS__))
// macro overloaded on number of argumnets, max 32
#define BINARY_N(\
_32,_31,_30,_29,_28,_27,_26,_25,_24,_23,_22,_21,_20,_19,_18,_17,_16,_15,_14,_13,_12,_11,_10,_9,_8,_7,_6,_5,_4,_3,_2,_1,\
N,...) BINARY##N
#define BINARY(...) BINARY_N(__VA_ARGS__,\
_32,_31,_30,_29,_28,_27,_26,_25,_24,_23,_22,_21,_20,_19,_18,_17,_16,_15,_14,_13,_12,_11,_10,_9,_8,_7,_6,_5,_4,_3,_2,_1,\
)(__VA_ARGS__)
int main() {
printf("%B\n", BINARY(0000,1111,0011,1100));
printf("%B\n", BINARY(1100));
printf("%B\n", BINARY(0101,1010,100));
// test 128 bits with gcc extensions - __auto_type and __uint128_t
const __auto_type bignum = BINARY(1111,1110,1101,1100,1011,1010,1001,1000,0111,0110,0101,0100,0011,0010,0001,0000,1111,1110,1101,1100,1011,1010,1001,1000,0111,0110,0101,0100,0011,0010,0001,0000);
printf("%llB%064llB\n", (unsigned long long)(bignum >> 64), (unsigned long long)bignum);
for (int i = 128/4; i; --i) {
printf("%04B%s", (unsigned)((bignum >> 4 * (i - 1)) & 0xf), i == 1 ? "\n" : ",");
}
}
Code outputs:
111100111100
1100
10110100100
11111110110111001011101010011000011101100101010000110010000100001111111011011100101110101001100001110110010101000011001000010000
1111,1110,1101,1100,1011,1010,1001,1000,0111,0110,0101,0100,0011,0010,0001,0000,1111,1110,1101,1100,1011,1010,1001,1000,0111,0110,0101,0100,0011,0010,0001,0000
Here is a portable macro (c99) that works for values up to 20 bits and supports blocks of bits of any length. The macro name is Ob
with an O
like Orange.
#include <stdio.h>
#include <stdlib.h>
/* macros to express binary constants using comma separated blocks of bits */
#if defined __STDC_VERSION__ && __STDC_VERSION__ >= 202000
// construct binary constant supported by C23 (up to 64 bits)
#define Ob1(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,...) 0b##a##b##c##d##e##f##g##h##i##j##k##l##m##n##o##p##q##r##s##t
#else
// use octal to construct binary constant up to 20 bits
#define Ob4(n) (int)((((n)>>6)&8)|(((n)>>4)&4)|(((n)>>2)&2)|((n)&1))
#define Ob20(n) ((Ob4((n)>>48)<<16)|(Ob4((n)>>36)<<12)|(Ob4((n)>>24)<<8)|(Ob4((n)>>12)<<4)|Ob4(n))
#define Ob1(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,...) Ob20(0##a##b##c##d##e##f##g##h##i##j##k##l##m##n##o##p##q##r##s##t##LL)
#endif
// Ob takes up to 20 blocks of binary bits
#define Ob(...) Ob1(__VA_ARGS__,,,,,,,,,,,,,,,,,,,)
#define TEST(e) printf("%32s: %d, %#.4x\n", #e, (int)(e), (int)(e))
int main(void) {
TEST(strtol("111100111100",NULL,2));
TEST(Ob(0000,1111,0011,1100));
TEST(Ob(1111,0011,1100));
TEST(Ob(111100,11,11,0,0));
TEST(Ob(1,1,1,1,0,0,1,1,1,1,0,0));
return 0;
}
Output:
strtol("111100111100",NULL,2): 3900, 0x0f3c
Ob(0000,1111,0011,1100): 3900, 0x0f3c
Ob(1111,0011,1100): 3900, 0x0f3c
Ob(111100,11,11,0,0): 3900, 0x0f3c
Ob(1,1,1,1,0,0,1,1,1,1,0,0): 3900, 0x0f3c
Not only binary
The separation issue applies with any lengthy number, be it binary, octal, decimal, hexadecimal. It applies to integers and floating point.
C23 offers the single quote character '.
@Ted Lyngmo
The separator is only used to separate digits and does not affect the value.
Valid sequences include:
uint32_t j = 0b1000'0110'0111'0101'0011'000'1001;
double my_pi = 3.141'592'653'589'793'238'462'643'383'279'5;
uint64_t x = 0x1'23'456'789A'BCDEF; // Separators need not be evenly spaced.
Invalid sequences include:
'0101 // Separator lacks a prior digit.
0101' // Separator lacks a trailing digit.
0'b1 // Separator is not directly between binary digits.
1.23'e45 // Separator lacks a trailing digit.
1.'23e45 // Separator lacks a leading digit.
1''23 // Adjacent separators not allowed.
int a = 0b1010'1100'0110'1001;
. – Weather Vane Commented Feb 3 at 17:50