Use of !! in C language

Hello,

I was browsing through the libraries and found the shift functions where a !! is used.

digitalWrite(dataPin, !!(val & (1 << i)));
else
digitalWrite(dataPin, !!(val & (1 << (7 - i))));

Now, from what I understand it flips the value twice, but I’d like to know if this is perhaps only an oversight or if there is a reason for this to be used.

Anyone?

It converts an integer to a boolean. More specifically any non-zero value is changed to 1.

You’ll notice that in boolean expressions zero means false and non-zero means true, but the
symbol “true” is defined to be 1. Thus if you want to rely on a truth value being a member of
the set {0, 1} you can use ! operator twice to enforce this.

! maps zero to true (ie 1) and anything else to zero.

Note that in the example the results where bit masks so not just in the set {0, 1}.

Consider for example:

  switch (truthvalue)
  {
  case false:
    break ;
  case true:
    break ;
  }

which you might expect to have complete coverage. In fact you’d be wise to go:

  switch (!!truthvalue)
  {
  case false:
    break ;
  case true:
    break ;
  }

I think it's an attempt to conform to sending HIGH to digitalWrite() where HIGH==1. But I think it's misguided, since digitalWrite will accept any non-zero value to produce a high level on the output pin.

aarg:
I think it’s an attempt to conform to sending HIGH to digitalWrite() where HIGH==1. But I think it’s misguided, since digitalWrite will accept any non-zero value to produce a high level on the output pin.

That is true for the current implementation, but in theory that might change.

The use of !! is just like MarkT states a way to enforce the values to the range {0,1} in C-style

alternative code would be

digitalWrite(dataPin, ? (val & (1 << i)) HIGH : LOW );
else
digitalWrite(dataPin, ? (val & (1 << (7 - i))) HIGH : LOW);

The ? a : x: y; notation is C construct for if a the x else y;

Rob, the question is not just what it does, but why it was done.

By the way, I like your implementation better, even though it is more verbose.

robtillaart:
That is true for the current implementation, but in theory that might change.

The use of !! is just like MarkT states a way to enforce the values to the range {0,1} in C-style

alternative code would be

digitalWrite(dataPin, ? (val & (1 << i)) HIGH : LOW );
else
digitalWrite(dataPin, ? (val & (1 << (7 - i))) HIGH : LOW);

The ? a : x: y; notation is C construct for if a the x else y;

condition ? true_value : false_value

digitalWrite(dataPin, (val & (1 << i)) ? HIGH : LOW );

A simple example that shows what those lines are doing:

void setup(void) {
int i;
char val=0x06;
char final;

  Serial.begin(9600);
  for (i=0; i<8; i++) {
    Serial.print(i);
    Serial.print(" - ");
    
    final = val & (1 << i);
    Serial.print(final, HEX);
    final = !final;
    Serial.print(" | ");
    Serial.print(final, HEX);
    final = !final;    
    Serial.print(" | ");
    Serial.println(final, HEX);
    delay(1000);
  }
   
}

void loop(void) {
}

The result is:

0 - 0 | 1 | 0
1 - 2 | 0 | 1
2 - 4 | 0 | 1
3 - 0 | 1 | 0
4 - 0 | 1 | 0
5 - 0 | 1 | 0
6 - 0 | 1 | 0
7 - 0 | 1 | 0

So the double ‘!!’ converts to boolean a char or integer value, like other user said.

To me, the fact that the OP asked the question suggests that it shouldn't be written this way. Why use such an obtuse way when "non-clever" code can work as well and be better documented and understood? If it's true that 80% of development cost is in testing/debugging, this code serves no useful purpose. If the coder worked for me, we'd be having a little heart-to-heart discussion.

Amen.

oqibidipo:
condition ? true_value : false_value

digitalWrite(dataPin, (val & (1 << i)) ? HIGH : LOW );

oops, you’re right
my mistake :wink:

Another alternative

digitalWrite( dataPin, (bool)(val & (1 << i)) );
         digitalWrite(dataPin, !!(val & (1 << i)));
      else   
         digitalWrite(dataPin, !!(val & (1 << (7 - i))));

sigh…
would there be less trouble if val was shifted instead of “i”?

        digitalWrite(dataPin, 1 & (val >> i));
      else   
         digitalWrite(dataPin, 1 & (val >> (7 - i)));

Kinda reconfirms what we said earlier... ;)

That is just a d@mned stupid way to code that function call. The most fundamental problem with the expression as used:

digitalWrite(dataPin, !!(val & (1 << i)));

is that the second argument to digitalWrite is NOT a boolean, it is an integer!

HIGH and LOW are defined in Arduino.h as:

#define HIGH 0x1
#define LOW  0x0

So, while that expression may work now, if the definitions of HIGH, LOW, TRUE or FALSE are ever changed, this code may well break. TRUE could just as well be defined as 0xffff, instead of 1. MOST code would not care about this change, but the code in question would no longer work as expected, and could prove difficult to debug, especially for newbies.

The whole point of defining constants like HIGH and LOW is so you will NOT use explicit numeric values in your code, but rather use the symbolic values. This allows the set of options to be re-defined or expanded in the future, without breaking code written before the change was made.

Some programmers seem to think using clever tricks like that makes them look smart. I believe it does exactly the opposite. More often than not, it makes them the authors of many very clever, and very hard to correct, bugs. I’ve been doing Engineering, and Engineering management for over 35 years, and I’ve always said: “A single very smart, but un-disciplined, engineer can do FAR more damage than a whole building full of very average, but disciplined, engineers”.

Regards,
Ray L.

Thanks for the replies.

Doubts answered.

guix:
Another alternative

digitalWrite( dataPin, (bool)(val & (1 << i)) );

That is not the same thing as using HIGH and LOW.

This example as well as all the others that use booleans abuse the digitalWrite() API interface.
The digitalWrite() interface is defined to take HIGH and LOW as a value parameter.
The fact that a particular digitalWrite() code implementation may work when using a non zero value instead of HIGH is not a valid reason to use it since using a non zero value to get a high level output is not part of the digitalWrite() API.
https://www.arduino.cc/en/Reference/DigitalWrite

It is possible that it may not work in all digitalWrite() implementations.

The proper way to use an API function is to use it the way the API has documented and defined it.
Once you step outside using an API the way it is the defined, you open your code up to potential future failures should the internal coding implementation change.

Therefore, the proper way to do it is as oqibidipo described:

digitalWrite(dataPin, (val & (1 << i)) ? HIGH : LOW );

all the other examples that are using booleans are abusing the API and not using digitalWrite() as it is documented.


My only guess as to why digitalWrite() was not originally defined to take a boolean is that it generates poor code on the AVR than using a uint8_t (or at least it used to) That is because booleans were essentially ints which are 16 bits on the AVR and the AVR doesn’t handle 16 bit values efficiently as 8 bit values.

BTW,
As a side note, it would be within the existing digitalWrite() API definition if digitalWrite() extended to support PWM
by changing HIGH to be defined as 255 and LOW as 0 and then LOW would be low output and HIGH would be a high output and any value between LOW and HIGH were a PWM output.
If that were done, all those applications that followed the digitalWrite() API properly and used HIGH and LOW would continue to work properly, but all those that abused the API by using non zero values instead of HIGH would have their code break.

— bill

I think in the past it has been found putting !!! in code like below may cause problems.

Serial.println("I found it, woohoo!!!");

What's wrong with

byte makeBool x = (( val != 0 ) ? HIGH : LOW );

?

GoForSmoke: What's wrong with

byte makeBool x = (( val != 0 ) ? HIGH : LOW );

makeBool ?? as a "standalone" keyword? What is that? That line doesn't look like valid C/C++ syntax to me.

The key thing to keep in mind that the actual values of HIGH and LOW cannot be assumed to be any particular value. To assume so violates the API interface definition. i.e. the API implementation should be free to assign any value it wants to the symbols HIGH and LOW and it should not break any code that properly uses the API.

The output of this code will not be what you expect.

The output is what I expect. But not what you expect.

@wired67, I suggest you actually run the test.