Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

chown33

Moderator
Staff member
Aug 9, 2009
10,753
8,441
A sea of green
Why do you think a macro is necessary? Outside of things that can only be determined at compilation time, you shouldn't be using #define.

(We have gotten way off topic.)
I used macros as an illustrative example, not because I thought a macro was necessary.

In other words, after seeing the effect of parens, I was reminded that writing macros can be perilous if they result in an unparenthesized expression.


EDIT
You can display the bits of a value with the following macro (you'll either need to #include <limits.h> or replace CHAR_BIT with 8):

Code:
#define printbits(x) (for(int __pb_ii=CHAR_BIT*sizeof(x); __pb_ii-->0;) putchar('0'+((x)&(1LL<<__pb_ii))))
Use it like this: printbits(average);
This macro doesn't compile. A 'for' statement can't be enclosed in parens. Only expressions can be enclosed in parens.

It will compile after replacing the first '(' with {, and the last ')' with ;}. Unfortunately, this doesn't make it work. The shifting also needs to be fixed, because the expression added to '0' isn't always just a 0 or 1.

The {} will preclude its use with the comma operator. There are other consequences as well, since the expansion is no longer an expression, but a {} block.
 
Last edited:

Qaanol

macrumors 6502a
Jun 21, 2010
571
11
Why do you think a macro is necessary? Outside of things that can only be determined at compilation time, you shouldn't be using #define.

(We have gotten way off topic.)

I am not sure who you are addressing, but I used a macro so you can call printbits() with any data type. To do it without a macro you would either need separate functions for every data type (printintbits(), printfloatbits(), printcharbits(), printdoublebits(), etc.) or you would need to pass a pointer and specify how many bytes to display:

Code:
void printbitsfn(void *p, size_t sz) {
  if (!p || !sz) return;
  char *v = (char *)p;
  for (int ii = 0; ii < sz; ii++;)
    for (int jj = CHAR_BIT; jj-->0;)
      v[ii] & (1 << jj) ? putchar('1') : putchar('0');
}

Then the usage is:

printbitsfn(&average, sizeof(average));

And you can't call that function directly on a literal. The macro can easily handle all data types up to 64 bits with a much simpler syntax and the ability to handle literals. If you need to print bits for a dense array, or if you have a variable named "__macro_printbits_ii" in scope, then the function is superior though.

Although, if I were doing it with a function I wouldn't directly print the bits, but instead return them in a c string:

Code:
char* getbits(void *p, size_t sz) {
  if (!p || !sz) return 0;
  int kk = sz * CHAR_BIT;
  char *c = malloc(kk + 1);
  if (!c) return 0;
  char *v = (char *)p;
  c[kk] = 0;
  for (int ii = sz; ii --> 0;)
    for (int jj = 0; jj < CHAR_BIT; jj++)
      c[--kk] = v[ii] & (1 << jj) ? '1' : '0';
  return c;
}

I think that should work.

I used macros as an illustrative example, not because I thought a macro was necessary.

In other words, after seeing the effect of parens, I was reminded that writing macros can be perilous if they result in an unparenthesized expression.


EDIT

This macro doesn't compile. A 'for' statement can't be enclosed in parens. Only expressions can be enclosed in parens.

It will compile after replacing the first '(' with {, and the last ')' with ;}. Unfortunately, this doesn't make it work. The shifting also needs to be fixed, because the expression added to '0' isn't always just a 0 or 1.

The {} will preclude its use with the comma operator. There are other consequences as well, since the expansion is no longer an expression, but a {} block.
Thanks for checking, that's what I get for writing code while away from a compiler. It should work though if we pull the usual macro trick and wrap it in do{}while(0)

Code:
#define printbits(x) do { \
  for(int __macro_printbits_ii = CHAR_BIT * sizeof(x); __macro_printbits_ii --> 0;) \
    ((x) >> __macro_printbits_ii) & 1 ? putchar('1') : putchar('0'); \
  } while(0)

And good catch with the value of the bit-shift expression. I've changed that around to fix it.

Edit: cleaned up functions to (hopefully) work properly with respect to chars being promoted to ints within subexpressions, regardless of sign bits.

Edit 2: updated macro to work with all simple types, even those longer than long long, such as long doubles.
 
Last edited:

Qaanol

macrumors 6502a
Jun 21, 2010
571
11
So of course it turns out that bit shifts are only valid on integer types. The function versions above still work on all types, but the macros are only good for char, short, int, long, and long long, and their unsigned variants.

It seems rather artificially-restrictive to disallow bit shifting of float types. I mean sure, it’s rarely desired, and the *(long *)&x idiom works most of the time, but it does not work on literals, and it does not work on long doubles because they are longer than any integer type. On my machine a long double is 16 bytes (128 bits) whereas a long long is the same as a long, only 8 bytes (64 bits).

If you needed to bit shift a long double you’d have to do some serious shenanigans.
 

gnasher729

Suspended
Nov 25, 2005
17,980
5,565
If you needed to bit shift a long double you’d have to do some serious shenanigans.

Instead: Make sure you use C++11 or C11. In the macro, start with

auto tmp_ = (x);
size_t numberOfBytes_ = sizeof (tmp_);
unsigned char* ptr_ = (unsigned char *)&tmp_;

and then you can print the bits of anything. If someone passes i++ or sin (1.0) as the parameter, it will get evaluated only once which doesn't hurt.
 

subsonix

macrumors 68040
Feb 2, 2008
3,551
79
It seems rather artificially-restrictive to disallow bit shifting of float types.

I don't think it is because C don't know anything about the underlying representation of the number. If the result differs depending on platform, then much of the point is lost. I guess for similar reasons right shift on signed integers are implementation defined.
 

Qaanol

macrumors 6502a
Jun 21, 2010
571
11
Instead: Make sure you use C++11 or C11. In the macro, start with

auto tmp_ = (x);
size_t numberOfBytes_ = sizeof (tmp_);
unsigned char* ptr_ = (unsigned char *)&tmp_;

and then you can print the bits of anything. If someone passes i++ or sin (1.0) as the parameter, it will get evaluated only once which doesn't hurt.
That doesn’t quite work. The auto variable with no type specifier defaults to int, so x is cast to an int just as though the line were int tmp_ = (int)(x).

To actually print the bits of a long double you have to use a function that takes it by reference along with a size specifier.

And if you want to shift its bits you’d probably treat it as a char array and do all sorts of fencing to make sure you don’t run off either end of the array during large bit-shifts.
 

chown33

Moderator
Staff member
Aug 9, 2009
10,753
8,441
A sea of green
That doesn’t quite work. The auto variable with no type specifier defaults to int, so x is cast to an int just as though the line were int tmp_ = (int)(x).

To actually print the bits of a long double you have to use a function that takes it by reference along with a size specifier.

And if you want to shift its bits you’d probably treat it as a char array and do all sorts of fencing to make sure you don’t run off either end of the array during large bit-shifts.

The 'auto' keyword only does type inference in C++11, not C11, as far as I can tell:
http://en.wikipedia.org/wiki/C++11#Type_inference
 

subsonix

macrumors 68040
Feb 2, 2008
3,551
79
T
To actually print the bits of a long double you have to use a function that takes it by reference along with a size specifier.

You could just take sizeof(x) directly and skip auto and the temp variable. That said, you could also use a function with a void pointer and a size parameter and avoid the macro all together, which sounds like a better option imo.
 

subsonix

macrumors 68040
Feb 2, 2008
3,551
79
And if you want to shift its bits you’d probably treat it as a char array and do all sorts of fencing to make sure you don’t run off either end of the array during large bit-shifts.

And what is the point of shifting it again?

An IEEE floating point value is made up of three distinct parts, it doesn't make "numeric" sense to shift all of them. And even if you could mask the exponent or mantissa and shift them individually, they differ in size between the different floating point types so no generic method would work. If your only interest is in the bit pattern, use an unsigned int or a bitmap. If you want to print, instead of shifting the value and bitwise ANDing it with 1, use a mask and shift the mask.
 

Qaanol

macrumors 6502a
Jun 21, 2010
571
11
And what is the point of shifting it again?
That’s up the programmer. Sure, most of the time it’s not what you want, but every once in a while you need to do something like the fast inverse square root algorithm when your hardware doesn’t natively support square roots of long doubles.

You're right. You can use the typeof operator in Xcode for C, Objective C, C++ and Objective C++:

typeof (x) y = x;

makes y a variable with the same type as the expression x.
Ooh, clever, that’s good to know. Thanks.
 

subsonix

macrumors 68040
Feb 2, 2008
3,551
79
That’s up the programmer. Sure, most of the time it’s not what you want, but every once in a while you need to do something like the fast inverse square root algorithm when your hardware doesn’t natively support square roots of long doubles.

The point here was to print the representation, and in fact the example above shifts an integer, not the floating point number. Looking at x86 side of things all shift instructions are on integers, you need to store a float as a 'bit pattern', then shift it to accomplish this, similar to the cast to int above.
 

gnasher729

Suspended
Nov 25, 2005
17,980
5,565
That’s up the programmer. Sure, most of the time it’s not what you want, but every once in a while you need to do something like the fast inverse square root algorithm when your hardware doesn’t natively support square roots of long doubles.

The quoted algorithm doesn't exactly help with square roots of long doubles, only with plain float. And it can break with an optimising compiler (accessing the representation of a float using a long* is undefined behaviour). And it doesn't shift floating point numbers, it shifts integers.
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.