Continuing the discussion from ‘if (!digitalRead(buttonPin))’ v ‘(digitalRead(buttonPin))’ . What is the difference?:
What you wanted:
The '!' operator may be applied to any expression to reverse the result when the expression is evaluated.
for example, "if (! (digitalRead(pin) == HIGH)"
regardless of the return value, the result of any C expression is either "true" or "not true".
The comparison that results in either true or not true is best contained with a set of parenthesis to be clear.
The thing to understand is the journey from the actual return value of digitalRead and the "true" and "not true".
The journey is in fact that the same, the evaluation of some values using C rules occurs in the processor itself.
It's up to your software to interpret in the moment what true means and what to do when find out it is true.
You should write an English-language (or your language) sentence and use those words and comparisons;
you could also "#define NOT !" so your code reads "if (NOT (digitalRead(pin))". tbh, this may or may not be true.
More than you wanted:
At this level, you are talking about the C language. Underlying this is how the C compiler works, and how
the machine language being executed by the processor changes based on decisions your code makes.
Any boolean expression is evaluated for a value of "true" or "false". The actual values should never matter.
Decisions in C language are the result of comparing two values; booleans are just a helpful special case.
At the machine language level,
several instructions get the values being compared into registers,
operations (math mostly) occur as instructions execute, the processor internally keeps track of things
instructions are executed that generate several internal results: zero, negative, error, ?.
jump instructions are "jump to this 32-bit value" or "jump depending on the internal value of the zero flag"
the instruction pointer, or, "pointer to the next instruction to execute in the processor", may be changed.
Pins may be written to or may be read from using the value HIGH or LOW. The actual values should never matter.
Obviously a 1-bit pin within the processor accessible with a special function can only have values of 0 and 1.
At the machine level all values are 32-bit values in registers, so all access and comparisons by machine
language are 32-bit values, which means all C variables and function return values end up as 32-bit values.
Pin values are not boolean, unless you learn the underlying way the C compiler implements booleans.
Once you know the underlying method, any thing that has a value can be forced into evaluation as a boolean.
Under these rules, "(digitalRead(pin) == HIGH)" is a boolean expression.
The '!' operator may be applied to any boolean expression to reverse the result when the expression is done.
for example, "if (! (digitalRead(pin) == HIGH)"
at the most fundamental instruction level of the processor itself, based on an internal flags, the next
instruction will either be the instruction that follows this instruction in the pipeline, or a jump instruction
to an instruction that hopefully is in the pipeline. Otherwise the location has to be accessed to begin fetching
the instructions to continue operating; if that operation is far away, the fetching operation may be very costly.
Once you know the underlying method and the way booleans work at the assembly level, read this part.
When you write C code that is "if (value1 == value2) " that causes the compiler to load and compare.
In the case where you have several operations, and "value1" is the result, and "value2" is the integer 0,
the compiler will know it has the result in value1 but it will still follow your direction to do the compare.
An interesting thing is the compiler has to create a value of 0 for the processor to make the comparison.
a more complex rule for booleans says the form "if (value1)" is a comparison of a value to be zero or not,
and most important, the idea that the value zero has the meaning "false", anything not zero is "true".
When using the "if (value1 == value2) value3=0;" form,
the comparison and the jump (around "value3=0;") based on the result will be emitted by the compiler.
In the case of "if (value1)", the compiler knows that it has performed operations and the result is in
value1. What it also knows is the result flags such as zero, negative, etc. are describing value1.
the comparison does not need to execute, so the compiler leaves that instruction out. Time saved.
Because a comparison to a register with the value zero does not need to execute, no register
needs to be assigned a run-time or compile-time value of zero for the comparison. Time saved.
Note also the number of instructions (16-bit or 32-bit at a time) that were eliminated .
What about saving the time it takes to jump to the next instruction? You must either jump or not jump.
At the C level, an expression followed by code will show how this jump is implemented. First comparison,
then the jump to the "this expression was not true" code (after the {} or to the else, out of for/while) occurs.
The "this expression was true" code directly follows the expression, in C and at the machine level. This is
a convention only, the compiler can do what it wants, but it is processing texts that human interact with
so the assumption that asking a question being true would normally be followed by action because it is true.
The person still awake will ask "what about return if zero" rather than "jump if zero": there is no "return"
instruction, instead the code retrieves the caller's return location from the stack or register and jumps there.
So choosing expressions based on the normal and expected outcome can eliminate a lot of time.
If you have for example a sequence of operations that each need to be checked before proceeding,
and you put it into a if/if/else/else structure, only one jump occurs during the success sequence of actions.
if (gadgetRead (something1))
{
if (gadgetRead (something2))
{
output "something1 something2"
// must jump over failure cases, but if they are returns from a function, may be able to just fall through
return
}
else
{
output "something2 failure"
return;
}
}
else
{
output "something1 failure"
return;
}
Avoid having a lot of implied knowledge built into the return value from gadgetRead and how it maps
into the true/false boolean paradigm. Write a C wrapper function or macro that has all the calls and checks for
return errors and knows everything about gadgetRead, then have it simply return true or false as a result.
The reason for this long explanation was to be able to point out to a knowledgeable (now at least) reader
that the IEEE coding standards for proper C software in any system requires the form "if (value1 == value2)".
It probably also requires that the comparison be made for the case that is likely to cause the for/while/function
to exit is the comparison so that is upfront and explicit in the code, I didn't read any further after the forced ==.
Thanks for your time and consideration.