Strange operator - what does this mean and/or do?

Go To Last Post
14 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi all,

 

I was looking at the file wiring_shift.cpp in my Arduino setup and saw something I've never seen before:

 

void shiftOut (int dataPin, int clockPin, uint8_t bitOrder, uint8_t val)
{
    uint8_t i;
    for (i = 0; i < 8; i++)  {
        if (bitOrder == LSBFIRST) {
            digitalWrite (dataPin, !! (val & (1 << i)));

        } else {
            digitalWrite (dataPin, !! (val & (1 << (7 - i))));
        }
        digitalWrite (clockPin, HIGH);
        digitalWrite (clockPin, LOW);
    }
}

 

Note the operators in red: They are exclamation points (the "not" operator). But there are two together... "not-not"?

 

As a test, I tried this:

 

int main (void)
{
    sei();
    Serial.begin (115200);
    STDIO.open (Serial);

    int x;
    int y;

    for (x = -5; x <= 5; x++) {
        y = !! (x);
        printf ("x: %2d, y: %2d\n", x, y);
    }
    while (1);
}

 

The output:

 


x: -5, y:  1
x: -4, y:  1
x: -3, y:  1
x: -2, y:  1
x: -1, y:  1
x:  0, y:  0
x:  1, y:  1
x:  2, y:  1
x:  3, y:  1
x:  4, y:  1
x:  5, y:  1


 

Ah ha! I think I know what's going on here... so I tried removing one of the exclamation points and got this output:

 


x: -5, y:  0
x: -4, y:  0
x: -3, y:  0
x: -2, y:  0
x: -1, y:  0
x:  0, y:  1
x:  1, y:  0
x:  2, y:  0
x:  3, y:  0
x:  4, y:  0
x:  5, y:  0


 

Then I said "what the heck" and tried FOUR exclamation points... and it yielded the same result as two.

 

So now I get what's going on here, but funny thing is I've NEVER seen this before, nor can I find any references to it online.

 

Is there any reason to use this (for example, like the wiring_shift.cpp code to output a high or low)? Does this result in faster code than a regular "test ? high : low" type of sequence? Has anyone else seen this before?

 

Comments and opinions will be appreciated.

 

 

Gentlemen may prefer Blondes, but Real Men prefer Redheads!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

In C false is 0  and if not 0 it's true :)

what you want here is to get 0 for false and 1 for true.

so first ! make the result 0 or 1.

because you don't really want to negate you put the 2. ! 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Without optimization, this would result in the following sequence of operations:

  • The result of the bitwise arithmetic undergoes type conversion to bool
  • The bool value is negated
  • The bool value is negated again
  • The bool value undergoes type conversion to uint8_t (type of the second argument to digitalWrite())

 

The reason for doing this is converting a bool to an integer type is guaranteed to result in the following (see the integral promotion section of this page):

  • false is converted to 0
  • true is converted to 1

 

This results in only 1 or 0 ever being passed as the second argument to digitalWrite(). With optimization, the results must be the same. static_cast<bool>( value ) should provide the same functionality and in my opinion would be clearer.

github.com/apcountryman/build-avr-gcc: a script for building avr-gcc

github.com/apcountryman/toolchain-avr-gcc: a CMake toolchain for cross compiling for the Atmel AVR family of microcontrollers

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

static_cast<bool>( value ), !!value, and value ? 0x01 : 0x00 all generate the same code when optimized (compiler explorer).

github.com/apcountryman/build-avr-gcc: a script for building avr-gcc

github.com/apcountryman/toolchain-avr-gcc: a CMake toolchain for cross compiling for the Atmel AVR family of microcontrollers

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

apcountryman wrote:

static_cast<bool>( value ), !!value, and value ? 0x01 : 0x00 all generate the same code when optimized (compiler explorer).

 

OMG!!! Compiler Explorer! I never knew there was such a thing online!

Gentlemen may prefer Blondes, but Real Men prefer Redheads!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Krupski wrote:
Has anyone else seen this before?

 

This is a pretty widely used and immediately recognizable idiomatic way to convert an arbitrary value to boolean value (in accordance with standard conversion rules). In C the target value would be 0 and 1, in C++ that would be `false` and `true`.

Dessine-moi un mouton

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Take a look at the Linux kernel code sometime, it's littered with !! as the standard way to convert 0/<any non-0 integer> into 0/1.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

apcountryman wrote:
static_cast<bool>( value ), !!value, and value ? 0x01 : 0x00 all generate the same code when optimized (compiler explorer).
IIRC this is one of the things for which avr-gcc oten get overly creative even when optimized.

How it gets overly creative depends on the precise expression used.

If the pattern (not really an idiom) is recognized, only three cycles should be required for a one-byte input,

four if there is actual need for the entire int.

 

Edit: corrected attribution

Iluvatar is the better part of Valar.

Last Edited: Mon. Jan 20, 2020 - 03:38 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I've been misquoted. "clawson" did not post the item quoted in #8 ?!?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

sparrow2 wrote:

In C false is 0  and if not 0 it's true :)

what you want here is to get 0 for false and 1 for true.

so first ! make the result 0 or 1.

because you don't really want to negate you put the 2. ! 

Yes I know that. The main reason I posted the question was that I had never seen the use of two not operators together before and I wondered what, if any benefit there was to using it.

About the only thing I saw it could be used for was setting a pin high or low depending on the value of a bitmask (for example, a synchronous serial write function).

I experimented with the double not operator vs "bitmask ? high : low" and a plain "if / then / else" and found no speed difference at all.

So, unless I'm missing something, there is no VALUE in using the double not operator....

Gentlemen may prefer Blondes, but Real Men prefer Redheads!

Last Edited: Thu. Jan 23, 2020 - 09:07 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

'Tis compact and easy to recognize.

Iluvatar is the better part of Valar.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Krupski wrote:
unless I'm missing something, there is no VALUE in using the double not operator....

Several of the posts here have described the VALUE - it gives you a guaranteed binary 0 or 1 from any arbitrary input value.

 

Please see Tip #5 in my signature (below; may not be visible on mobile)

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

>So, unless I'm missing something, there is no VALUE in using the double not operator....

 

There really is no value left for !!, except to point out you are probably looking at legacy code. In this case digitalWrite uses a uint8_t to represent a bool, and in this case, only tests for 0/LOW, so anything else results in the pin set to 1/high which means no conversion to 0/1 is needed. BUT, if some young whippersnapper decides they should test for 1/HIGH instead in digitalWrite (and forgot about shiftOut), and the caller is assuming 0=LOW/not 0=HIGH, then of course it breaks. The solution is to use a bool when you wanted a bool (digitalWrite argument), then the conversion to bool is done by the compiler when needed and you eliminate all manual conversion whether by !! or ternary or whatever. Bool has a history (or lack of), but has been around for a long time now.

 

You also probably found the only usage of !! in arduino code (grep it I guess).

 

 

>Take a look at the Linux kernel code sometime, it's littered with !! as the standard way to convert 0/<any non-0 integer> into 0/1.

 

Which probably just points out the legacy it came from. Grep for bool, and you will see 40x the usage. All the !! left is most likely old code or code that has to interact with old code or asm code (just a guess).

 

This is not that old of an addition  (#17)-

https://www.kernel.org/doc/html/latest/process/coding-style.html

so I imagine there was kicking and screaming along the way and they probably realized all the old compilers were in the dustbin and most users have been using bool for a long time anyway, so may as well give in and recommend it.

Last Edited: Thu. Jan 23, 2020 - 07:24 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
Last Edited: Thu. Jan 23, 2020 - 08:39 PM