Shifting array elements in C++

I‘m working on a problem to enter and edit an unsigned number (1 - 5 digits) with a rotary encoder and an LCD. The number could be „12“ or „86576“, so turning the knob alone is not very comfortable. It‘s better to do it digit by digit. The encoder and menu part already is working with test integers. I want it to start with either a default value (4 digits) or the last value entered (1-5 digits) – and here is my problem. I must fill an array in a way that always uses 5 index elements, even if the number has less digits. Otherwise, if the last number entered had 4 digits, I can‘t add/edit the 5th digit in the menu:

int myNumber = 1234;

int myArray[5];

should be:

myArray[0] = 0;	// This must be added, if number is 4 digit only
myArray[1] = 1;
myArray[2] = 2;
myArray[3] = 3;
myArray[4] = 4;

With php this is a no-brainer, but C++ is still new territory for me. The following code gets the digits of an existing integer into an array. How can I shift the elements 1-4 to always get 5 array members (I need leading 0‘s)

int32_t noOfSteps = 1234;   // Could be 1, 534, 86789
uint8_t nr[5];              // The array to hold the single digits


uint8_t countDigits(uint32_t x) {
  uint8_t number_of_digits = 0;
  do {
    ++number_of_digits;
    x /= 10;
  } while (x);
  return number_of_digits;
}

uint8_t getDigit(uint32_t from, uint8_t index)
{
  return (from / (int)pow(10, floor(log10(from)) - index)) % 10;
}

void setup() {
  Serial.begin(9600);
  uint8_t noOfDigits = countDigits(abs(noOfSteps));
  for(int i = 0; i < noOfDigits; i++) {
    nr[i] = getDigit(noOfSteps, i);
    Serial.print("Index ");Serial.print(i);Serial.print(": "); Serial.println(nr[i]);
  }
}

void loop() {

}

The console output:

Index 0: 1
Index 1: 2
Index 2: 3
Index 3: 4

An idea Doing an itoa() in base 10 on your number will get your data into a char array (cString) in ASCII and then you can just read each ascii entry of the array, subtract ‘0’ (the character zero in ASCII) and you have your digit value.

(you can use strlen() to find the length of the cString)

You can also take the modulo by 10 to get units, then divide your value by ten (in integer math) and repeat to extract each digit

x = 1234
Digit0 = x % 10; // this gives you the 4
x = x / 10; // x is now 123
Digit1 = x % 10; // this gives you the 3
x = x / 10; // x is now 12
...

Or maybe just this :

  int32_t noOfSteps = 6789;   // Could be 1, 534, 86789
  uint8_t nr[5];                    // The array to hold the single digits
  nr[4] =  noOfSteps % 10 ;
  nr[3] = (noOfSteps / 10 ) % 10 ;
  nr[2] = (noOfSteps / 100 ) % 10 ;
  nr[1] = (noOfSteps / 1000 ) % 10 ;
  nr[0] = (noOfSteps / 10000 ) % 10 ;

Does the encoder have an integral pushbutton and are you able to use it?

You could make the encoder add/subtract other powers of ten than 100.

This method would propagate to the higher powers on digit wrap, but that I think that is even desirable.

Thanks for your answers...

@ J-M-L
@ 6v6gt
If you look closer to the function I posted, you'll notive that it's doing exactly what you recommend. This part already is solved.

@ dougp
Yes, the encoder has a push-button. I already can edit/change any digit of any number, but if the number has only 4 digits, I can't edit the 5th. That's the reason for my post.

@ Whandall
Yes, that would be possible, but changing the base would complicate the entry of small numbers. The user must be able to add/edit any number between 1 and 99999.

Demokrit:
Yes, that would be possible, but changing the base would complicate the entry of small numbers. The user must be able to add/edit any number between 1 and 99999.

No, I think it is less complicated than juggling around arrays, indices and many conversions back and forth
and you can add/edit any number between 1 and 99999 by adding/subtracting 10x (0 <=x <= 4).

If you manipulate one of the digits of a number you add/subtract 10x to that number.

Admitting up front I haven't looked carefully at all responses. Maybe this will break something loose.

Putting aside the behind-the-curtain stuff, this is what the user sees: Upon entry to this mode the default/existing number is displayed. The ones digit is blinking (I assume you can command the LCD this way). Turning the encoder varies the blinking digit value - which is constrained to 0-9. Pressing encoder button advances to blink tens digit. Encoder is turned... lather, rinse, repeat. Blinking wraps to ones after ten-thousands. Long press on encoder button signals acceptance of displayed value.

Internally, your array holds each user-set 0-9 digit. A function reads this array and prints each digit in turn to the LCD. When the number is accepted the digits are collected and processed to produce a 4-byte long value.

Exactly dougp, that is the current state. I can edit all digits that are existing. Here starts my problem. I always want to start at the last value entered (or, during first run, a 4-digit default value). If this (old) entry has 5 digits, everything is fine. But if it's only 4 digits, there is no 5th to edit. What I need is shown in the first code block of my initial post. Last value was "int myNumber = 1234;", and the array must have "0" at first array position.

I don’t understand your issue
Why can’t you get 0 in the first entry if you have only 4 digits ?

The maths with divide and modulo will give you that 0

@J-M-L

That would be true if I could pass something like “01234” to my digit-separation function. But that would be no valid integer.

I found a solution, which is really ugly:

void setup() {
 Serial.begin(9600);
 uint8_t noOfDigits = countDigits(abs(noOfSteps)); // 1...5
 int i = 0;
 int digitsNr = noOfDigits;

 while(digitsNr < 5){
   nr[i] = 0;
   digitsNr++;
   i++;
 }
 int startIdx = i;
 for(i = 0; i < noOfDigits; i++) {
   nr[startIdx] = getDigit(noOfSteps, i);
   startIdx++;
 }
}

Ther’s probably a better way to do it, but I’m still struggling with C++ arrays.

If you pass 1234 as a number the math will work

J-M-L,

you were right. My function to fetch the digits returned strange results in some cases (like digit=0). I replaced it by a lookup table:

// Get i-th digit from an number up to 5 digits (zero-based index)
// Expand if needed
int getDigitFromInt(const int32_t n, const int i) {
    switch(i)
    {
        case 0:return n%10;
        case 1:return n/10%10;
        case 2:return n/100%10;
        case 3:return n/1000%10;
        case 4:return n/10000%10;
//        case 5:return n/100000%10;
//        case 6:return n/1000000%10;
//        case 7:return n/10000000%10;
//        case 8:return n/100000000%10;
//        case 9:return n/1000000000%10;
    }
    return 0;
}

To fill my array in the way I need, I used:

    // Enforce 5 digits
    for (int i = 4; i >= 0; i--) {
      nr[4-i] = getDigitFromInt(noOfSteps, i);      
      Serial.print("Index "); Serial.print(i); Serial.print(": "); Serial.println(nr[i]);
    }

Now everything works like charme.

Thanks for you help!

I know you solved your problem, but an alternative that could also work would be to use the technique described in this blog post:

Hi marco,

using logarithmic increments was my first approach, but my number range (1...99999) is too big to use it efficiently. I'm currently using it for fields with a range of 1..100.