Using if (!!(expr)) instead of if (expr)
Asked Answered
B

5

65

While reading the example code provided by Texas Instruments for their SensorTag I came across the following snippet.

void SensorTagIO_processCharChangeEvt(uint8_t paramID) { 
    ...

    if (!!(ioValue & IO_DATA_LED1)) {
        PIN_setOutputValue(hGpioPin, Board_LED1, Board_LED_ON);
    } else {
        PIN_setOutputValue(hGpioPin, Board_LED1, Board_LED_OFF);
    }

    if (!!(ioValue & IO_DATA_LED2)) {
        PIN_setOutputValue(hGpioPin, Board_LED2, Board_LED_ON);
    } else {
        PIN_setOutputValue(hGpioPin, Board_LED2, Board_LED_OFF);
    }

    if (!!((ioValue & IO_DATA_BUZZER))) {
        Clock_start(buzzClockHandle);
    }
    ...
}

The declaration is like this (in the same file).

#define IO_DATA_LED1   0x01
static uint8_t ioValue;

Does if (!!(ioValue & IO_DATA_LED1)) offer any advantage over if (ioValue & IO_DATA_LED1)?

Bahia answered 20/2, 2016 at 12:8 Comment(4)
https://mcmap.net/q/24562/-what-does-x-mean-in-c-esp-the-linux-kernel/3049655Clodhopping
@CoolGuy: That is not a dup. There is a reason the argument in the linked question is converted to a boolean value. Here it is useless, as the value itself is not processed.Cagliostro
@Olaf No, __builtin_expect(x, 0) and __builtin_expect(!!(x),0) should behave equally, hence it is useless there, tooThoth
Of course, none of the answers presented here so far provide any insight into why if (!!((ioValue & IO_DATA_BUZZER))) { would contain double parens around the bitwise-and. Things like that make me wonder if we aren't just seeing code that's suffered cut-and-paste / search-and-replace damage.Raber
L
80

Applying the logical not (!) operator twice has the purpose of normalising a value to be either 0 or 1. In a control expression of an if-statement, that doesn't make any difference. The if-statement only cares about the value being zero or nonzero, the little !! dance is completely useless.

Some coding style guides might mandate this kind of dance, which could be the reason why the TI code you posted does it. I haven't seen any that do so though.

Leakey answered 20/2, 2016 at 12:9 Comment(15)
Agreed. It is very uncommon in embedded programming, too. I suspect that is some relic from unrolling e.g. a BITTEST macro which is supposed to return the value of a bit, not only the set/clear information for further processing. Not such code is often written by students, etc. which are themselves not very experienced (and not very well payed), so they tend to use existing patterns and don't optimise/enhance well.Cagliostro
@Olaf FWIW I've already seen code like that written by experienced devs. In Java :OWeakminded
@GOTO0: Which might make them experienced Java devs, but not experienced C devs.Cagliostro
Does the C spec actually mandate that !0 == 1, or is it just a minor detail that all contemporary implementations happen to hold? In the latter case, I'd expect a modern C compiler to toss the double negation out entirely.Silvester
@Olaf As Java is a strongly typed language, there is no other operand type than booleans that logical negation can be applied to, making double negation useless. The only possible reason I can think of might be to force a NPE when applied to boxed booleans.Silvester
@JanDvorak: That does not even matter here! But yes, a logical operator yields an int with either 0 or 1, as my comment actually implies.Cagliostro
@Olaf The only point I was trying to make is that it's not always possible to infer the degree of experience of a programmer from the code that they write.Weakminded
I have seen the !! being used in competitive programming, for reading input. (will try to find a code snippet)Ostosis
Some compilers warn about constructs like if (a = b), since these are likely to be erroneous. You could avoid the warning with if ((a = b) != 0), but that is ugly as sin. if (!!(a = b)) is somewhat more concise.Specialist
@Specialist Usually, compilers pick up if ((a = b)). I think this convention dates back to lint.Leakey
@GOTO0: Sure, but it is possible to infer the coding abilities of a programmer from the code they write. Being an "experienced" programmer is a meaningless qualification, there are far too many terrible programmers with years of experience.Ephesus
@BlueRaja-DannyPflughoeft I was talking of people with 15+ years working experience writing nonsense like if (!!(...)). There's no reason to assume that only underpaid students could produce poor code.Weakminded
I'd say it's less of a Java thing and more of a JavaScript thing. Also think I've seen that in Python. But in no variety of C/C++/C# etc. or Java is it in any way helpful that I am aware of.Craigcraighead
@DarrelHoffman you probably haven't seen it python, since python has no ! operator, it uses not.Nail
@porglezomp: Oops, maybe I was thinking of PHP? Perl? One of the P-languages anyhow. I don't use them much. Definitely seen this in JavaScript though. It's so common almost acceptable there.Craigcraighead
D
55

The expression !!x, or !(!x), means 1 if x is a true value (a nonzero number or a nonnull pointer), otherwise 0. It is equivalent to x != 0, and it's nearly the same as C99 (_Bool)x but available in compilers that predate C99 or whose developers have chosen not to implement C99 (such as cc65 which targets the MOS 6502).

The conditional as a whole is equivalent to the following:

if (ioValue & IO_DATA_LED1) {
    /* what to do if the IO_DATA_LED1 bit is true */
} else {
    /* what to do if the IO_DATA_LED1 bit is false */
}

In C, it means "if the bitwise AND of those two values is nonzero, execute the block."

But some coding style guides might prohibit a bitwise AND (&) at the top level of an if statement's condition, assuming it to be a typo for the logical AND (&&). It's in the same class of mistakes as using = (assignment) instead of == (equality comparison), for which many compilers offer a diagnostic. GCC Warning Options describes diagnostics like these:

-Wlogical-op: Warn about suspicious uses of logical operators in expressions. This includes using logical operators in contexts where a bit-wise operator is likely to be expected.

-Wparentheses: Warn if parentheses are omitted in certain contexts, such as when there is an assignment in a context where a truth value is expected

Use of a paraphrase such as (a & B) != 0, (_Bool)(a & B), or !!(a & B) communicates to the compiler and to other developers that use of a bitwise operator was intentional.

See also a related answer about !!x in JavaScript.

Dakota answered 20/2, 2016 at 16:25 Comment(5)
Agreed. I immediately looked at OP's code and saw bitmasking - the !! really makes that clear.Nessim
Thanks! for the explanation.Bahia
This is a better answer than the accepted answer, which does not address the bitwise operator aspect.Newsmagazine
Note about "It (!!x) is equivalent to x != 0, or to the C99 (_Bool)x" --> differs in type and maybe size.Cecrops
This seems to be a better answer than the accepted one.Hollandia
S
16

In MSVC converting an integer to a bool implicitly in an if statement can generate a warning. Doing so via !! does not. Similar warning may exist in other compilers.

So supposing the code was compiled with that warning enabled, and a decision to treat all warning as errors, using !! is a short and portable way to say "yes, I want this integer to be a bool".

Sunny answered 20/2, 2016 at 17:20 Comment(1)
But... the control expression of an if statement isn't implicitly converted. Perhaps you are confusing C with C++?Leakey
R
4

Although silencing the compiler warning for the bit-wise & is the most likely, this looks like it could also be the result of a refactoring to add enums for readability from:

PIN_setOutputValue(int,int,bool); //function definition
PIN_setOutputValue(hGpioPin, Board_LED1,!!(ioValue & IO_DATA_LED1));
PIN_setOutputValue(hGpioPin, Board_LED2,!!(ioValue & IO_DATA_LED2));
//note: the !! is necessary here in case sizeof ioValue > sizeof bool
//otherwise it may only catch the 1st 8 LED statuses as @M.M points out

to:

enum led_enum {
  Board_LED_OFF = false,
  Board_LED_ON = true
};
PIN_setOutputValue(int,int,bool); //function definition
//...
PIN_setOutputValue(hGpioPin, Board_LED1,!!(ioValue & IO_DATA_LED1)?Board_LED_ON:Board_LED_OFF);
PIN_setOutputValue(hGpioPin, Board_LED2,!!(ioValue & IO_DATA_LED2)?Board_LED_ON:Board_LED_OFF);

Since that exceeded the 80 character limit it was then refactored to

if (!!(ioValue & IO_DATA_LED1)) {
    PIN_setOutputValue(hGpioPin, Board_LED1, Board_LED_ON);
} else {
    PIN_setOutputValue(hGpioPin, Board_LED1, Board_LED_OFF);
}

if (!!(ioValue & IO_DATA_LED2)) {
    PIN_setOutputValue(hGpioPin, Board_LED2, Board_LED_ON);
} else {
    PIN_setOutputValue(hGpioPin, Board_LED2, Board_LED_OFF);
}

Personally I would have preferred the initial version for readability, but this version is common when lines of code are used as a metric (I'm surprised it didn't declare variables for each state, set each state separately and then use that).

The next version of this "Best Practice" code might look like:

bool boardled1State;
bool boardled2State;
//...

boardled1State = !!(ioValue & IO_DATA_LED1);
boardled2State = !!(ioValue & IO_DATA_LED2);
//...

if (boardled1State) {
    PIN_setOutputValue(hGpioPin, Board_LED1, Board_LED_ON);
} else {
    PIN_setOutputValue(hGpioPin, Board_LED1, Board_LED_OFF);
}

if (boardled2State) {
    PIN_setOutputValue(hGpioPin, Board_LED2, Board_LED_ON);
} else {
    PIN_setOutputValue(hGpioPin, Board_LED2, Board_LED_OFF);
}
//... and so on

All of that could have been done like this:

for (int i=0;i<numleds;i++)
        PIN_setOutputValue(hGpioPin, i ,!!(ioValue & (1<<i)));
Regine answered 24/2, 2016 at 23:44 Comment(2)
You didn't mention explicitly, but the reason !! is present in PIN_setOutputValue(hGpioPin, Board_LED1,!!(ioValue & IO_DATA_LED1)); is because the function will be declared to take some sort of integer type parameter (maybe int, or unsigned char for example) but if the flag has a large value, then even if the test succeeds the result may be converted to 0Kloman
@Kloman good point, I guess that isn't obvious. I'm just used to platforms with typedef char bool;, so didn't even think to mention thatRegine
C
3

OP is looking at some old coding idiom - which made some sense BITD (back-in-the day).

  1. A primary use of !! was to handle C implementations that converted the expression in an if(expr) to int rather than testing against zero.

Consider what happens when expr is converted to int and then tested against 0. (Since C89, this is non-conforming as the test should be a direct test against 0)

int i;
long li;
double d;

// no problems
if (i & 5) ...
if (d > 4.0) ...

// problems
if (li & 0x10000) ...  (Hint: int is 16-bit)
if (d)                 (d might have a value outside `int` range.

// fix
if (!!(li & 0x10000))
if (!!d)

So on pre C89 compliers and non-conforming C89 and later, using !! coped with that weakness. Some old habits take a long time to die.

  1. In early C++, there was no bool type. So code that wanted to test for trustfulness needed to use the !! idiom

    class uint256;  // Very wide integer
    uint256 x;
    
    // problem  as (int)x may return just the lower bits of x
    if (x) 
    
    // fix
    if (!!x) 
    
  2. What happens with C++ today (I know this is a C question) when there is no (bool) operator defined, is not the (int) operator used? This results in the same problem as #2. As for many early years C and C++ code bases were kept in sync, using !! had relevance with constructs like if (!!x).


Using !! works today but has certainly fallen out of favor as it solves problem that no longer occurs with any significant frequency.

Cecrops answered 21/2, 2016 at 23:30 Comment(2)
!! is still used in conforming C89 when passing the result of the test to a functionKloman
@Kloman Agree, that is a useful application of !!. The post, though, centers on use with if().Cecrops

© 2022 - 2024 — McMap. All rights reserved.