Home > OS >  Rewriting a specific if-statement
Rewriting a specific if-statement

Time:07-05

I am currently involved in a C language based embedded project where we have the following logic for component control:

input1 input2 output
OFF OFF NO_MOVEMENT
OFF ON DIRECTION_B
ON OFF DIRECTION_A
ON ON ERROR

Currently, in the existing code it is implemented like this:

//input1 and input2 are unsigned int and output is enum

if((input1 == 0u) && (input2 == 0u)) {
    output = NO_MOVEMENT;
}
else if((input1 == 1u) && (input2 == 0u)) {
    output = DIRECTION_A;
}
else if((input1 == 0u) && (input2 == 1u)) {
    output = DIRECTION_B;
}
else {
    output = ERROR;
}

I am wondering how to rework this piece of code to make it more optimal and elegant. My proposal is:

if(input1 == 0u)
{
    output = (input2 == 0u) ? NO_MOVEMENT : DIRECTION_B; 
}
else
{
    output = (input2 == 0u) ? DIRECTION_A : ERROR; 
}

Is it fine or maybe there is some better option?

CodePudding user response:

The current code looks quite clear and readable to me when all you have is two variables with two distinct values - I wouldn't change it for the alternative you suggested, it looks perfectly ok. If anything, maybe use a lookup table.

static const int input_output_table[2][2] = {
      [OFF][OFF] = NO_MOVEMENT,
      [OFF][ ON] = DIRECTION_B,
      [ ON][OFF] = DIRECTION_A,
      [ ON][ON] =  ERROR
   };

output = input_output_table[input1][input2];

(do any sanity checks if you need to ensure input1/input2 can be something other than 0 or 1. Alternatively coerce them to booleans as output = input_output_table[!!input1][!!input2];

CodePudding user response:

In my opinion, the original approach is fine enough. For easy reading, there is also possibility to use switch ... case with a small adjustment.

AltInput = (input1 << 1) | input2;

Then we can define an enum like following:

enum:
NO_MOVEMENT = 0,
DIRECTION_B,
DIRECTION_A,
ERROR

Then later you could use:

switch (AltInput)
{
case NO_MOVEMENT:
...
break;
case DIRECTION_B:
...
break;
case DIRECTION_A:
...
break;
default:
...
break;
}

CodePudding user response:

If the reason for rewriting this is performance, then note that the output values is a binary sequence and you can just merge input1 and input2 to get a number. Consider this solution:

typedef enum
{
  NO_MOVEMENT,
  DIRECTION_B,
  DIRECTION_A,
  ERROR,
  MASK = 0x3 // just defensive programming, you might not need it
} output_t;

output_t get_output (unsigned int input1, unsigned int input2)
{
  return (input1<<1 | input2) & MASK;
}

Benchmarked on gcc/x86, it's 4 instructions branch free (and a very likely candidate for inlining):

get_output:
        add     edi, edi
        or      edi, esi
        mov     eax, edi
        and     eax, 3
        ret

Without defensive programming:

typedef enum
{
  NO_MOVEMENT,
  DIRECTION_B,
  DIRECTION_A,
} output_t;

output_t get_output (unsigned int input1, unsigned int input2)
{
  return input1<<1 | input2;
}

Disassembly:

get_output:
        lea     eax, [rdi rdi]
        or      eax, esi
        ret

You won't beat this performance even with a LUT.

  • Related