bitRead(), bitWrite() and unsigned long long

All,

I’m having difficulties getting bitRead() and bitWrite() to work with unsigned long long (i.e. 64 bit). Am I missing something? Is there a way around this?

unsigned long long x;

void setup() {
  Serial.begin(9600);
}

void loop() {
  bitWrite(x, 35ULL, 1ULL);
  if (bitRead(x, 35ULL) == 1ULL)
    Serial.println("One");
  else
    Serial.println("Zero");
}

Above sketch prints “Zero” !

OK, so I could write:

x = 1ULL << 35;

which works, but BitWrite() doesn’t. Is it not just a macro?

Thanks,

Paul

PaulRB: I'm having difficulties getting bitRead() and bitWrite() to work with unsigned long long (i.e. 64 bit). Am I missing something? Is there a way around this?

These macros are written to work for 32-bit variables.

But you can easily look up the source code and create another set of macros that work with 64-bit variables.

Thanks Jurs.

I was hoping that, being macros rather than functions, they would work with any size integer independant of size, and it surprised me when they didn't. I can code without them.

Paul

The parameter of those functions which specifies which bit you are reading and writing, is going to be a number between 0 and 31. Or 0 and 63. A quite small number. You could use a 1-byte integer for it. There is no need to specify a 64-bit value for this function parameter.

Actually, it's quite annoying that the actual type of the arguments of these functions and the function return value are not easily found.

Here’s your problem:

#define bitSet(value, bit) ((value) |= (1UL << (bit)))
#define bitClear(value, bit) ((value) &= ~(1UL << (bit)))

Now if that was 1ULL it would probably work.

… tests …

Yes it does, see modified sketch below:

unsigned long long x;

void setup() {
  Serial.begin(115200);
}

#undef bitSet
#undef bitClear
#define bitSet(value, bit) ((value) |= (1ULL << (bit)))
#define bitClear(value, bit) ((value) &= ~(1ULL << (bit)))

void loop() {
  bitWrite(x, 35, 1);
  if (bitRead(x, 35) == 1)
    Serial.println("One");
  else
    Serial.println("Zero");
    
  while (true) {}
}

You can do it with templates, which are therefore type safe:

unsigned long long x;

void setup() {
  Serial.begin(115200);
}

#undef bitRead
#undef bitWrite

template <typename T> void bitWrite (T & value, byte bit, byte val);  // prototype
template <typename T> void bitWrite (T & value, byte bit, byte val)
  {
  if (val)
    value |= (T(1) << bit);
  else
    value &= ~ (T(1) << bit);
  }

template <typename T> bool bitRead (T value, byte bit);  // prototype
template <typename T> bool bitRead (T value, byte bit)
  {
  return  (value & ((T)1 << bit)) != 0;
  }

void loop() {
  bitWrite(x, 35, 1);
  if (bitRead(x, 35) == 1)
    Serial.println("One");
  else
    Serial.println("Zero");
    
  while (true) {}
}

There is no need to specify a 64-bit value for this function parameter.

Thanks michinyon, yes I know but was trying "ULL" on every combination of constants in desparation! Sometimes you have to put "UL", "ULL" etc on a small value like 1 to force the compiler to use the size of int you want.

Nick, thanks for the suggested code. I was thinking a macro would not know or need to know the types it is working with, but I didn't think about constants in the macro which of course do.

You can do it with templates, which are therefore type safe:

That's a neat solution. Is there a reason why they are not defined like that as standard in the Arduino IDE?

Paul

PaulRB:
That’s a neat solution. Is there a reason why they are not defined like that as standard in the Arduino IDE?

The runtime for function calls, putting parameters on the stack, executing the function code and returning a value from the function is much longer than runtime for just the bit manipulation inside the code without a function call. So programs using slow function calls instead of quick bit manipulations might run into trouble with performance problems, if it is of relevance, how many bit manipulations per second can be executed.

So if execution time is important, you’d better use the code version from Nicks reply #4 and not the templates version from his reply #5.

jurs:
The runtime for function calls, putting parameters on the stack, executing the function code and returning a value from the function is much longer than runtime for just the bit manipulation inside the code without a function call. So programs using slow function calls instead of quick bit manipulations might run into trouble with performance problems, if it is of relevance, how many bit manipulations per second can be executed.

So if execution time is important, you’d better use the code version from Nicks reply #4 and not the templates version from his reply #5.

Nah, you’ll find the template is compiled inline and most probably equivalent. BitRead may need some help. By changing the input value to a constant reference I’m willing to bet there is no difference.

template bool bitRead (const T &value, byte bit);

The benefit of a function over a define is it allows you to effectively select whether you want it inlined or not.

I added some consts but they didn’t seem to make any difference:

template <typename T> void bitWrite (T & value, const byte bit, const byte val);  // prototype
template <typename T> void bitWrite (T & value, const byte bit, const byte val)
  {
  if (val)
    value |= (T(1) << bit);
  else
    value &= ~ (T(1) << bit);
  }

template <typename T> bool bitRead (const T & value, const byte bit);  // prototype
template <typename T> bool bitRead (const T & value, const byte bit)
  {
  return  (value & ((T)1 << bit)) != 0 ;
  }

My sketch #4 took 2026 bytes and sketch #5 took 2032 bytes.

However looking at the generated code it looked like the compiler had, in both cases, optimized out the calls and worked out the answer at compile-time.

Changing “x” to be volatile, thus forcing a runtime calculation, made both sketches exactly the same size: 2138 bytes.

I had a quick run in 1.5.7 using the new compiler, and the tests were equally sized at 1918 bytes, even without my proposed change.

However in 1.5.6 using the old compiler, it was slightly slower as well.