Bitshift programming error or a misunderstanding?

I am attempting to explore aspects of bitwise math to learn more. In this situation I am just trying to use the left bitshift command to loop and print each iteration of a single bit shift. However, I am not achieving what I try and wonder if it is because I misunderstand something fundamental.

In the first step, I tried shifting a five bit number of type "byte", and terminating the loop right after. Instead, the loop runs to the full length of byte (eight bits). Does the logic treat the missing three bits as there but just undisplayed?

byte z;

void setup() {
 Serial.begin(9600);
}
void loop() {
for ( z = 0b00001; z > 0b0; z <<= 1){
   Serial.println(z,BIN);
  delay(1000);
}
}

To test that idea out, I changed the conditional of the for loop to

z > 0b0100

which I'd expect to terminate the loop in two cycle, but instead nothing at all occurs.

Where are the fundamental flaws in my understanding?

I searched the forum and online pretty rigorously and cant seem to identify my fault.

You want z < 0b0100, i.e. loop while z is less than 4. z > 0b0100 is "loop while z is greater than 4", which is false the very first time thru.

For your first scenario you can try the following loop()

void loop()
{
  for (z = 0b00001; z > 0b0; z <<= 1)
  {
    Serial.println(z, BIN);
    delay(1000);
  }
  Serial.println(F("end of or-loop"));
}

And observe the behaviour; it might explain what you see.

For your second scenario you will not see anything because 0b00001 is not greater than 0b0100 so the for-loop will not be entered.

Serial.print does not print leading zeros, so if you print 0b00000101, you will only see 101.

You misunderstanding seems to be thinking that the internal representation of a number has anything to do with how you typed it in the assignment statement. z = 1, z = 0001, z = 0x1, and z = 0b0000001 all result in the same actual value in the variable.

So the byte always has 8 bits.

You could do:

for (z = 1; z < (1<<5); z <<= 1)
  {
    Serial.println(z, BIN);
    delay(1000);
  }

Check this:

byte z = 0b00001;

void setup() {
  Serial.begin(9600);
}
void loop() {
   for ( uint8_t n=0; n<5; n++)
   {
//  for ( z = 0b00001; z > 0b0; z <<= 1) {
    Serial.println(z, BIN);
    z=z<<1;
    delay(1000);
  }
  while(1); // stop processing 
}

Also Remember that a byte is unsigned and thus always >= 0

You are fine here as you don’t include 0

But it’s a frequent mistake we see

Yes, a byte is 8 bits always, integer types aren't stretchy like a string, they are rigid, mapping directly onto the chip's hardware registers and memory locations.

I'd prefer

for (unsigned z = 1 ; z <= 0b10000 ; z <<= 1)

Using unsigned rather than byte allows the compiler to choose the "natural" size of integer which may be more efficient (though not always). "unsigned" means the same as "unsigned int".

Try to always declare a variable at the point it is used - you declared z globally, but it seems only to be relevant to the loop, so its best to declare it in the loop.

in C++ types smaller than int (like uint8_t) are promoted to int before any arithmetic or bitwise operation.

So if I were to write the loop
for (uint8_t z = 1; z <= 0b10000; z <<= 1),
the operation z <<= 1 is performed with z promoted to int, then the result is converted back to uint8_t before being stored in z.

This means the expression z <<= 1 is not done directly on an 8-bit type but on an int (16 or 32 bits depending on our common platforms), then truncated to 8 bits.

If we use your unsigned z, the promotion has no effect since z is already an int size.

The question is more how you use z afterwards in the loop, having more than 8 bits might or might not be an issue esp. if you want to use it as a mask (although here z <= 0b10000 will ensure you are always with a null MSB)

Check - GitHub - RobTillaart/printHelpers: Arduino library to help formatting data for printing

bin(some_number) will print with leading zero's

The operation is preformed AS IF arguments were promoted to int/uint.
Even at -O0, the compiler is smart enough to realize that it doesn't actually have to do the promotion...

void main() {                                                                   
  for (uint8_t z = 1; z <= 0b10000; z <<= 1) {                                  
;; z = 1                                                                        
   a:   81 e0           ldi     r24, 0x01       ; 1                             
   c:   89 83           std     Y+1, r24        ; 0x01                          
   e:   08 c0           rjmp    .+16            ; 0x20     
                                                                                
;;    PORTB = z;                                                                
  10:   85 e2           ldi     r24, 0x25       ; 37                            
  12:   90 e0           ldi     r25, 0x00       ; 0                             
  14:   29 81           ldd     r18, Y+1        ; 0x01                          
  16:   fc 01           movw    r30, r24                                        
  18:   20 83           st      Z, r18                                          
                                                                                
;; z <<= 1                                                                      
  1a:   89 81           ldd     r24, Y+1        ; 0x01                          
  1c:   88 0f           add     r24, r24   ;; equiv to shift                                     
  1e:   89 83           std     Y+1, r24        ; 0x01                          
                                                                                
;; z <= 0b10000                                                                 
  20:   89 81           ldd     r24, Y+1        ; 0x01                          
  22:   81 31           cpi     r24, 0x11       ; 17                            
  24:   a8 f3           brcs    .-22            ; 0x10   
    PORTB = z;                                                                  
  }                                                                             
}

It does better at -Os, except that it replaces the comparison of z with a counted loop (using an int!) (an "optimization" I find particularly annoying (uses registers!) and unnecessary):

void main() {                                                                   
   0:   85 e0           ldi     r24, 0x05       ; 5                             
   2:   90 e0           ldi     r25, 0x00       ; 0                             
                                                                                
  for (uint8_t z = 1; z <= 0b10000; z <<= 1) {                                  
   4:   21 e0           ldi     r18, 0x01       ; 1                             
    PORTB = z;                                                                  
   6:   25 b9           out     0x05, r18       ; 5                                                             
   8:   22 0f           add     r18, r18        ;; shift                                
   a:   01 97           sbiw    r24, 0x01       ; 1                             
   c:   e1 f7           brne    .-8             ; 0x6                                                                      
  }                                                                             
}

Fair

My point was really that we need to be careful when conducting operations with bytes.