More Efficient Math

Hello all,

I am working on a 4-digit, 7-segment display that will show a number between 0 and 200, which reflects an efficiency percentage between a planned count and an actual count. The planned and actual counts max out at 99999999 and the percent rounds up or down to the nearest whole percent. I have the following code, using floats for the numbers, but the math is so intensive that the display flickers each time the count changes. How can I improve it to reduce the time?

void calculateEfficiency() {
  if (plannedTotal > 0 && actualTotal > 0) {
    float percent = (actualTotal / plannedTotal) * 1000.0;
    effTenths = 0;
    effOnes = 0;
    effTens = 0;
    effHundreds = 0;
    if (percent >= 2000.0) {// Efficiency can't be greater than 200%
      effOnes = 0;
      effTens = 0;
      effHundreds = 2;
    }
    else {
      for (unsigned int i = 0; i < percent; i++) {
        if (effTenths >= 9) {
          effTenths = 0;
          if (effOnes >= 9) {
            effOnes = 0;
            if (effTens >= 9) {
              effTens = 0;
              if (effHundreds >= 9) effHundreds = 0;
              else effHundreds++;
            }
            else effTens++;
          }
          else effOnes++;
        }
        else effTenths++;
      }
    }
    if (effTenths >= 5) {// round up or down...
      if (effOnes >= 9) {
        effOnes = 0;
        if (effTens >= 9) {
          effTens = 0;
          if (effHundreds >= 9) effHundreds = 0;
          else effHundreds++;
        }
        else effTens++;
      }
      else effOnes++;
    }
  }
}

I think the flcker does not come from the maths in this function but on the way you refresh the display. This is not present in the snippet of code you provided, so it's not easy to help. But, I believe that your loop runs very fast and updates the display each time, hence the flicker.

In order to reduce this, you have 2 possible solutions (among others...):

  • delay() the execution of the loop (the lazy way)
  • update the display only if it changes: store the previous value, compare it to the current one and update if different

lesept:
I think the flcker does not come from the maths in this function but on the way you refresh the display. This is not present in the snippet of code you provided, so it's not easy to help. But, I believe that your loop runs very fast and updates the display each time, hence the flicker.

In order to reduce this, you have 2 possible solutions (among others...):

  • delay() the execution of the loop (the lazy way)
  • update the display only if it changes: store the previous value, compare it to the current one and update if different

The way I refresh the display... is not based on the execution of this code, but based on a timed multiplexing of each digit, common cathode, at a nominal refresh rate of 120Hz. Although it is a 4-digit display, it draws 6 digits to match the intensity of 6-digit displays in close proximity, although there is nothing connected to those cathodes. 1 digit is lit for 1388 microseconds before moving to the next. It only flickers at the time this code executes and I am guessing that it takes longer than 1.388 milliseconds to divide 2 floats and break the result into 4 bytes, then execute a rounding, at least the way that I'm doing it, which is why I need some help.

      for (unsigned int i = 0; i < percent; i++) {

If efficiency is 100% then this loop must execute 1000 times! Don't do that.

  percent += 0.5; //for rounding
  effHundreds = percent/1000;
  percent -= effHundreds*1000;
  effTens = percent/100;
  percent -= effTens*100;
  ...

While floating point division is relatively slow, it is much faster than counting up to 1000 by ones.

For extra credit, try to do it without the factor of 1000.

I was thinking of sth similar to Morgan's proposition:

int effTenths, effOnes, effTens, effHundreds;

void calculateEfficiency(float percent) {
  //  if (plannedTotal > 0 && actualTotal > 0) {
  //    float percent = (actualTotal / plannedTotal) * 1000.0;
  Serial.print(percent);
  percent = (percent + 0.05) * 10;
  effTenths = 0;
  effOnes = 0;
  effTens = 0;
  effHundreds = 0;
  percent = constrain(percent, 0, 2000);
  effHundreds = percent / 1000;
  percent -= 1000 * effHundreds;
  effTens = percent / 100;
  percent -= 100 * effTens;
  effOnes = percent / 10;
  effTenths = percent - 10 * effOnes;
  Serial.println(" = " + String(effHundreds) +
                 " " + String(effTens) +
                 " " + String(effOnes) +
                 " " + String(effTenths));
  //  }
}

void setup() {
  Serial.println(115200);
  while (!Serial);
  calculateEfficiency(1);
  calculateEfficiency(50.123);
  calculateEfficiency(112.654);
  calculateEfficiency(26.456);
  calculateEfficiency(99.999);
  calculateEfficiency(-1);
}

void loop() {

}

This provides the following output on the monitor:

15:00:37.816 -> 1.00 = 0 0 1 0
15:00:37.816 -> 50.12 = 0 5 0 1
15:00:37.816 -> 112.65 = 1 1 2 7
15:00:37.816 -> 26.46 = 0 2 6 5
15:00:37.816 -> 100.00 = 1 0 0 0
15:00:37.816 -> -1.00 = 0 0 0 0

which I expect is what you want...?

I see I'm a bit late but will add this to the pile anyway:

void calculateEfficiency() 
{
    if (plannedTotal > 0 && actualTotal > 0) 
    {
        float percent = (actualTotal / plannedTotal) * 1000.0;
        if( percent - (int)percent >= 0.5 )
            percent += 0.5;
        
        if( percent > 2000.0 )
            percent = 2000.0;

        effHundreds = (int)(percent/1000.0);
        percent = percent - (effHundreds * 1000);
        effTens = (int)(percent/100.0);
        percent = percent - (effTens * 100);
        effOnes = (int)(percent/10.0);
        percent = percent - (effOnes * 10);
        effTenths = (int)percent;

    }//if
    
}//calculateEfficiency

If you have flicker problems, maybe you can post your entire code for assistance.

Perehama:
The way I refresh the display... is not based on the execution of this code, but based on a timed multiplexing of each digit, common cathode, at a nominal refresh rate of 120Hz. Although it is a 4-digit display, it draws 6 digits to match the intensity of 6-digit displays in close proximity, although there is nothing connected to those cathodes. 1 digit is lit for 1388 microseconds before moving to the next. It only flickers at the time this code executes and I am guessing that it takes longer than 1.388 milliseconds to divide 2 floats and break the result into 4 bytes, then execute a rounding, at least the way that I'm doing it, which is why I need some help.

Use a timer interrupt to clock the multiplexer. That way there's no flicker no matter how long any one function blocks, since it will be done in the background.

The other option, if you still need to reduce computation time, is to use integers for fixed point math. Since you're dealing with a very small range of magnitudes in this computation the full functionality of floating point arithmetic is just wasteful.

MorganS:
If efficiency is 100% then this loop must execute 1000 times! Don't do that.

Thank you all. Here is my implemented code. It does execute much faster, and the display is smooth.

void calculateEfficiency() {
  effOnes = effTens = effHundreds = 0;
  if (plannedTotal > 0 && actualTotal > 0) {
    float percent = ((actualTotal / plannedTotal) * 100.0) + 0.5;
    if (percent >= 200) {// Efficiency can't be greater than 200%
      effHundreds = 2;
    }
    else {
      effHundreds = percent / 100;
      percent -= effHundreds * 100;
      effTens = percent / 10;
      percent -= effTens * 10;
      effOnes = percent;
    }
  }
}

I really like how simple the rounding was, as I had made it all too complex. That's one for my tool bag for the future.

Perehama:
The planned and actual counts max out at 99999999 and the percent rounds up or down to the nearest whole percent.

int percentage (long actualTotal, long plannedTotal)
{
  if (plannedTotal > 1000000)
  {
    actualTotal /= 100 ;  // scale to prevent overflow
    plannedTotal /= 100 ;
  }
  return (actualTotal * 100 + plannedTotal/2) / plannedTotal ;  // round to nearest
}

If you wish to get at the digits of a quotient, well, there's always the direct approach:

Just make sure that your values are not so large that the multiplication by 10 causes overflow.