Cutting A Number Into Several Integers

Hello,
I have 3 seven segments which can be controlled one by one, for example you say the module one to show number 3 and module 2 to show number 1 and you get number 31. I don't want to get into the seven segment, I just wanted to tell you my application.

Now I need to show a number like 165 on 3 seven segments, I have to put each digit of 165 into 3 int variables, to be like:

int First = 5;
int Second = 6;
int Third = 1;

and the show the numbers on the seven-segments one after another, but my value is changing, I need to constantly cut it into several numbers.
how should I do that?

Using the / and % operators?

So there are a lot of possibilities, easiest is:

int first = number / 100; // 1.65 => since int, cuts down to 1
int second = (number - (first * 100) ) / 10  //  6.5 => since int, cuts down to 6
int third = number - ( first * 100) - (second * 10) // just 5

This can be obviously optimized with % operator, just like AWOL mentioned!

dr-o:
So there are a lot of possibilities, easiest is:

int first = number / 100; // 1.65 => since int, cuts down to 1

int second = (number - (first * 100) ) / 10  //  6.5 => since int, cuts down to 6
int third = number - ( first * 100) - (second * 10) // just 5






This can be obviously optimized with % operator, just like AWOL mentioned!

Oh wow its easier than what I thought, thanks it helped me a lot.

dr-o:

int first = number / 100; // 1.65 => since int, cuts down to 1

int second = (number - (first * 100) ) / 10; //  6.5 => since int, cuts down to 6
int third = number - ( first * 100) - (second * 10); // just 5




This can be obviously optimized with % operator, just like AWOL mentioned!

The optimization:

int first = (number / 100) % 10; // 1
int second = (number / 10) % 10;  //  6
int third = number % 10; // 5

johnwasser:
The optimization:

int first = (number / 100) % 10; // 1

int second = (number / 10) % 10;  //  6
int third = number % 10; // 5

This got me real curious, because in a multi-plexed display, getting out of the loop as fast as possible is important, so I did some testing... I have the following method as part of a 7-segment display class.... which I added toggling PD6 so I could time it on a scope.

void sevenSegDisplay::printLZ(unsigned long n) {
  PORTD |= (1 << PD6);
  byte b[8];
  b[0] = (n / 10000000) % 10;
  b[1] = (n / 1000000) % 10;
  b[2] = (n / 100000) % 10;
  b[3] = (n / 10000) % 10;
  b[4] = (n / 1000) % 10;
  b[5] = (n / 100) % 10;
  b[6] = (n / 10) % 10;
  b[7] = n % 10;
  if (_nD > 6) {
    _digSegData[0] = _SLUT[b[0]];
    _digSegData[1] = _SLUT[b[1]];
    _digSegData[2] = _SLUT[b[2]];
    _digSegData[3] = _SLUT[b[3]];
    _digSegData[4] = _SLUT[b[4]];
    _digSegData[5] = _SLUT[b[5]];
    _digSegData[6] = _SLUT[b[6]];
    _digSegData[7] = _SLUT[b[7]];
  }
  else if (_nD > 4) {
    _digSegData[0] = _SLUT[b[2]];
    _digSegData[1] = _SLUT[b[3]];
    _digSegData[2] = _SLUT[b[4]];
    _digSegData[3] = _SLUT[b[5]];
    _digSegData[4] = _SLUT[b[6]];
    _digSegData[5] = _SLUT[b[7]];
  }
  else if (_nD > 2) {
    _digSegData[0] = _SLUT[b[4]];
    _digSegData[1] = _SLUT[b[5]];
    _digSegData[2] = _SLUT[b[6]];
    _digSegData[3] = _SLUT[b[7]];
  }
  else {
    _digSegData[1] = _SLUT[b[6]];
    _digSegData[2] = _SLUT[b[7]];
  }
  PORTD &= ~(1 << PD6);
}

It takes 257.4 microseconds from PD6 going HIGH to PD6 going LOW.
So, I changed the "optimization" to have only 1 modulo...

void sevenSegDisplay::printLZ1(unsigned long n) {
  PORTD |= (1 << PD6);
  byte b[8];
  n %= 100000000;
  b[0] = n / 10000000;
  n -= b[0] * 10000000;
  b[1] = n / 1000000;
  n -= b[1] * 1000000;
  b[2] = n / 100000;
  n -= b[2] * 100000;
  b[3] = n / 10000;
  n -= b[3] * 10000;
  b[4] = n / 1000;
  n -= b[4] * 1000;
  b[5] = n / 100;
  n -= b[5] * 100;
  b[6] = n / 10;
  n -= b[6] * 10;
  b[7] = n;
  // ...
  PORTD &= ~(1 << PD6);
}

It takes 152.0 microseconds from PD6 going HIGH to PD6 going LOW.
So, I changed again to use snprintf...

void sevenSegDisplay::printLZ2(unsigned long n) {
  PORTD |= (1 << PD6);
  char b[9];
  snprintf(b, sizeof(b), "%08u", n);
  if (_nD > 6) {
    _digSegData[0] = _SLUT[(b[0] - '0')];
    _digSegData[1] = _SLUT[(b[1] - '0')];
    _digSegData[2] = _SLUT[(b[2] - '0')];
    _digSegData[3] = _SLUT[(b[3] - '0')];
    _digSegData[4] = _SLUT[(b[4] - '0')];
    _digSegData[5] = _SLUT[(b[5] - '0')];
    _digSegData[6] = _SLUT[(b[6] - '0')];
    _digSegData[7] = _SLUT[(b[7] - '0')];
  }
  else if (_nD > 4) {
    _digSegData[0] = _SLUT[(b[2] - '0')];
    _digSegData[1] = _SLUT[(b[3] - '0')];
    _digSegData[2] = _SLUT[(b[4] - '0')];
    _digSegData[3] = _SLUT[(b[5] - '0')];
    _digSegData[4] = _SLUT[(b[6] - '0')];
    _digSegData[5] = _SLUT[(b[7] - '0')];
  }
  else if (_nD > 2) {
    _digSegData[0] = _SLUT[(b[4] - '0')];
    _digSegData[1] = _SLUT[(b[5] - '0')];
    _digSegData[2] = _SLUT[(b[6] - '0')];
    _digSegData[3] = _SLUT[(b[7] - '0')];
  }
  else {
    _digSegData[1] = _SLUT[(b[6] - '0')];
    _digSegData[2] = _SLUT[(b[7] - '0')];
  }
  PORTD &= ~(1 << PD6);
}

It takes 86.2 microseconds from PD6 going HIGH to PD6 going LOW.
I know time is only one metric, but I'd like to know what is the fastest way possible to do this...

It's hard to accept, because programmers are hard wired to avoid repetition, but in fact, successive subtraction is both faster, and more memory efficient than using % and /.

It's been a while since I did the testing using micros(), but the results were 4x-5x speed advantage:

void intTo4DigitArray (int val, uint8_t* result) {
  result[0] = 0;
  result[1] = 0;
  result[2] = 0;
  result[3] = 0;
  while (val >= 1000)
  {
    result[0]++;
    val -= 1000;
  }
  while (val >= 100)
  {
    result[1]++;
    val -= 100;
  }
  while (val >= 10)
  {
    result[2]++;
    val -= 10;
  }
  result[3] += val;
}

For testing, I used the worst case, 9999.

aarg:
It's hard to accept, because programmers are hard wired to avoid repetition, but in fact, successive subtraction is both faster, and more memory efficient than using % and /.

It's been a while since I did the testing using micros(), but the results were 4x-5x speed advantage:

I implemented this as follows, and it clocked at 52.08 microseconds. Impressive. Thank you.

void sevenSegDisplay::printLZ3(unsigned long n) {
  PORTD |= (1 << PD6);
  byte b[8] = {0, 0, 0, 0, 0, 0, 0, 0};
  while (n >= 100000000) n-= 100000000;
  while (n >= 10000000) {
    b[0]++;
    n -= 10000000;
  }
  while (n >= 1000000) {
    b[1]++;
    n -= 1000000;
  }
  while (n >= 100000) {
    b[2]++;
    n -= 100000;
  }
  while (n >= 10000) {
    b[3]++;
    n -= 10000;
  }
  while (n >= 1000) {
    b[4]++;
    n -= 1000;
  }
  while (n >= 100) {
    b[5]++;
    n -= 100;
  }
  while (n >= 10) {
    b[6]++;
    n -= 10;
  }
  b[7] += n;
  if (_nD > 6) {
    _digSegData[0] = _SLUT[b[0]];
    _digSegData[1] = _SLUT[b[1]];
    _digSegData[2] = _SLUT[b[2]];
    _digSegData[3] = _SLUT[b[3]];
    _digSegData[4] = _SLUT[b[4]];
    _digSegData[5] = _SLUT[b[5]];
    _digSegData[6] = _SLUT[b[6]];
    _digSegData[7] = _SLUT[b[7]];
  }
  else if (_nD > 4) {
    _digSegData[0] = _SLUT[b[2]];
    _digSegData[1] = _SLUT[b[3]];
    _digSegData[2] = _SLUT[b[4]];
    _digSegData[3] = _SLUT[b[5]];
    _digSegData[4] = _SLUT[b[6]];
    _digSegData[5] = _SLUT[b[7]];
  }
  else if (_nD > 2) {
    _digSegData[0] = _SLUT[b[4]];
    _digSegData[1] = _SLUT[b[5]];
    _digSegData[2] = _SLUT[b[6]];
    _digSegData[3] = _SLUT[b[7]];
  }
  else {
    _digSegData[1] = _SLUT[b[6]];
    _digSegData[2] = _SLUT[b[7]];
  }
  PORTD &= ~(1 << PD6);
}

Not sure how fast it is, but here's another subtraction version:

unsigned long value = 8426;
const int maxDigits = 4;

unsigned long subtrahend = 1;
unsigned long valueCopy;
unsigned long subCopy;
int digit[maxDigits];
int i;

void setup() {
  Serial.begin(9600);
  delay(1000);
  Serial.println(value); Serial.println();
  for (i = maxDigits; i > 1; --i) {
    subtrahend = subtrahend * 10;
  }
  
  valueCopy = value;
  subCopy = subtrahend;
  
  for (i = (maxDigits - 1); i > 0; --i) {
    digit[i] = 0;
    while (valueCopy >= subCopy) {
      valueCopy -= subCopy;
      digit[i]++;
    }
    subCopy = subCopy / 10;
    Serial.println(digit[i]);
  }
  digit[0] = valueCopy;
  Serial.println(digit[0]);
}

void loop() {
}

Perehama:
I implemented this as follows, and it clocked at 52.08 microseconds. Impressive. Thank you.

Wow, that looks horrendous! I'm impressed something that looks so complex runs makes such a good speed optimisation.

ShermanP:
Not sure how fast it is, but here's another subtraction version:

unsigned long value = 8426;

const int maxDigits = 4;

unsigned long subtrahend = 1;
unsigned long valueCopy;
unsigned long subCopy;
int digit[maxDigits];
int i;

void setup() {
  Serial.begin(9600);
  delay(1000);
  Serial.println(value); Serial.println();
  for (i = maxDigits; i > 1; --i) {
    subtrahend = subtrahend * 10;
  }
 
  valueCopy = value;
  subCopy = subtrahend;
 
  for (i = (maxDigits - 1); i > 0; --i) {
    digit[i] = 0;
    while (valueCopy >= subCopy) {
      valueCopy -= subCopy;
      digit[i]++;
    }
    subCopy = subCopy / 10;
    Serial.println(digit[i]);
  }
  digit[0] = valueCopy;
  Serial.println(digit[0]);
}

void loop() {
}

Sure, you can factor the code and it looks somewhat better. The one I posted is canonical, I have another version somewhere that does a similar job, uses a loop to cycle through the digits, instead of handling each one sequentially, explicitly. I think the execution time would be very similar.

aarg:
Sure, you can factor the code and it looks somewhat better. The one I posted is canonical, I have another version somewhere that does a similar job, uses a loop to cycle through the digits, instead of handling each one sequentially, explicitly. I think the execution time would be very similar.

"for" loops with initialization of an automatic variable cost a lot in time. There is the initialization, conditional check, and increment that are actually redundant to simply iterating over a line written x times. It looks cleaner, like you said in an earlier post, we don't like excessive code redundancy, but it executes slower.

Well here's a somewhat more efficient for-loop version. The subtrahends are calculated and stored into an array in advance, so repeated numbers can be parsed with no multiplications or divisions. Also, transferring array variables to/from temp variables saves time overall even though it adds extra steps. This version records the micros() value at the beginning and end of the parsing routine and prints the difference. To process the number 999999, I get a difference of 60. That's on a Nano.

unsigned long value = 999999;
const int maxDigits = 6;

unsigned long subtrahend = 1;
unsigned long valueCopy;
int digit[maxDigits];
int i;
unsigned long microBegin, microEnd;
unsigned long subtra[maxDigits];
unsigned long subTemp;
int digiTemp;

void setup() {
  Serial.begin(9600);
  delay(1000);
  Serial.println(value); Serial.println();
  for (i = 0; i < maxDigits; ++i) {
    subtra[i] = subtrahend;
    subtrahend = subtrahend * 10;
  }

  noInterrupts();
  microBegin = micros();
  
  valueCopy = value;
  
  for (i = (maxDigits - 1); i > 0; --i) {
    digiTemp = 0;
    subTemp = subtra[i];
    while (valueCopy >= subTemp) {
      valueCopy -= subTemp;
      digiTemp++;
    }
    digit[i] = digiTemp;
  }
  digit[0] = valueCopy;

  microEnd = micros();
  interrupts();
  
  for (i = (maxDigits); i > 0; --i) {
    Serial.println(digit[i-1]);
  }
  Serial.println(); Serial.println(microEnd - microBegin);
}

void loop() {
}