I am trying to gain mastery of combining smaller 8 and 10 bit variables into a single 32 bit variable using shift and logical operators. I have been getting unintuitive results.
Boiling things down to basics, I verified that the serial monitor would print a 32 bit varable in binary. Then I began experimenting with shifting a 1 to the left. Shifting any more than 13 positions to the left will result in output I do not understand. The following are my experiments and results.
Any light-shedding would be greatly appreciated. Thanks!
NOTE: I did not run more than one of these code attempts at a time. For forum readability I un-commented all code.
void setup() {
Serial.begin(9600);
}
void loop() {
uint32_t x = 0b10000000000000000000000000000001;
//works as expected 1, 30 zeroes, and a 1
uint32_t x = 1 << 32;
//Fails, prints "0" - 1 >> 31 fails too
uint32_t x = x | (1 << 8);
//Works - prints "10000000" - expected
uint32_t x = 0xFFFFFFFF;
//works expected , prints 32 1's ""11111111111111111111111111111111"
uint32_t x = 0xFFFFFFFF & 0xF0F0F0F0;
//works as expected "prints 111100001111000011110000
uint32_t x = 1 << 14;
//fails: prints 17 1's and 15 0's "11111111111111111000000000000000"
uint32_t x = 1 << 13;
//works as expected "prints a 1 and 13 0's "10000000000000"
Serial.println(x,BIN);
}
Well I spotted one untruth. Your mistake not that of the code, compiler or processor.
It would be better to have all the code uncommented so it computes then prints every value. Then copy/paste both the code and your output here.
It is highly unlikely that you are not the one making the mistakes or misinterpreting either or both the inputs and outputs of your experiment.
Experiments of which I tots approve, srsly, but in order to recreate and all be on the same page don’t make us do the work. If you want us to see what you saying.
It would make it easier to run on other machines, too.
@anon57585045 that’s the one. I heard a whooosh sound when I read that.
Kinda stopped paying too much close attention after that.
I was confident I was making a glaring Programming 101, mistake. I am very surprised this issue has resulted in a request for run-able code, now I don't feel so inept. Thanks.
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
Serial.println("Start");
}
void loop() {
uint32_t x = 0b10000000000000000000000000000001; //works
Serial.println(x,BIN);
x = 1 << 32;
// Fails prints "0"
Serial.println(x,BIN);
x = x | (1 << 8);
//Works - prints "10000000" - expected
Serial.println(x,BIN);
x = x | (1 << 17);
//Fails, prints a single "0"
Serial.println(x,BIN);
x = 0xFFFFFFFF;
//works, prints 32 1's ""11111111111111111111111111111111"
Serial.println(x,BIN);
x = 0xFFFFFFFF & 0xF0F0F0F0;
//works as expected "prints 111100001111000011110000
Serial.println(x,BIN);
x = 1 << 13;
//works as expected "prints a 1 and 13 0's "10000000000000"
Serial.println(x,BIN);
while(1){}
}
This does not fail. It does exactly what you asked it to do. The bit is in position zero, and the highest bit is bit 31. If you shift it 32 times, it exceeds the capacity of an unsigned long integer. The result is, and should be, zero.
Just FYI, he meant "why do you not use bitwise operators", or "why do you use boolean operators". Edit - okay now I see, no you are using bitwise operators which is correct.
The default type for expressions is int. The only non-type named override is for numeric constants, that I can think of. For example the expression "2354949" is recognized as a long so it's okay, but "23423 * 21353" is recognized as an int expression because its elements are ints, so the multiplication overflows because the result is too large for an int.
Understood, Thanks. And by "fail" I do mean, I have, personally failed to create code that will meet my needs and expectations.
32 too much - but my code still fails with any shift argument ranging from 14 up to 31. I am not able to synthesize a reason in my mind given the content in this thread. I have failed, not you.
Are you saying the, e.g. 1>>31 in my failed expression is an int? Why does that matter, if I shift it 31 times, my understanding is it loses its leading zeroes.
I am not understanding. By nature of binary (multiply by 2 to shift)are you saying the output of the expression overflows the uint32_t?
Can anyone please tell me how to move a 1 from position 0, to position 31 in a uint32_t? Example code would be greatly appreciated.
I know that an int is 16 bits, and I believe int is assumed. I thought I had that point covered at the top of my void loop where I declare x as a uint32_t. Do I need to declare a type for the 1 in 1 << n ?
I did, it fills bit 15 to bit 32 with 1's. But I do not understand why this variety was illustrative. It is among the problems I observed that led me here.
x = x | 1UL << 30;
This worked (placed a 1 in bit position 31). I think I can do my project now, however, if anyone has any suggestions for further reading, or keywords I can use to search for EDU material on this I would appreciate it. In other words, my concept of this is I was wrongly overfilling the target x with a 16 bit type, so to avoid that problem use a 32 bit type. i.e. If the bucket is overflowing, just double the amount of water. You see, I need to do a lot of reading, if someone has a goto web page, I would be grateful to know about it.
When you get to 15 bits, the '1' has gone into the sign bit of the 16-bit int and that makes it a very large negative number (-32768). When that negative number is converted to uint32_t (unsigned long int) it gets sign-extended to 32 bits first, then treated as unsigned:
15: 11111111111111111000000000000000
Beyond 15 bits the '1' has gone off the top end of the 16-bit int and the result is 0.
16: 0
17: 0
18: 0
19: 0
20: 0
21: 0
22: 0
If you use '1ul' instead of '1' for your literal things will work as you expect up to 31. After that the 1 bit goes off the top of the 32-bit integer and the result is 0.
Warning: 'byte' variables (uint8_t) get automatically promoted to SIGNED int (int16_t) so if you take a byte and shift it left so bit 15 (the sign bit) is set, that will be sign-extended. For example:
byte val = 0xAA;
// You might expect val<<8 stored in a uint32_t
// to have the value 0x0000AA00:
uint32_t result = val << 8;
// But what you get is 0xFFFFAA00;
Serial.println(result, HEX);
Myself, I just google "C++" with some language element I'm interested in, and choose my tutorial tujour from the list. This way you can bookmark any you like.
John, Thank you. Your generosity, professionalism and knack for teaching will be payed forward by me when I go to help other forum members coming up behind me on the curve.
These concepts are starting to gel. Signing and negative numbers are a key knowledge gap underlying my problem. I have read your uint32_t result = val << 8; example about 20 times. I'll need to manipulate your example, and do some studying. My understanding is wonky; val is an int with a 1 in bit 8. Because it is int, when bit 16 turns to a 1 due to the shift, it becomes negative and when fed into result converts result into a signed long. A sign bit can not be in the middle of a variable, so the sign bit jumps to bit 32. Clearing up my partial grasp of how result became 0xFFFFAA00 will be a Saturday well spent following aarg's study advice.
It seems like int and signs are sticky, and must be programmatically suppressed according to need.
That doesn't sound like the best philosophy. Language features are not moles to be whacked down. If you always use the correct data types and operations for the data and operations that you create, you won't normally have to be very concerned about it.
Good programmers also don't litter their code with tricky stuff like shift and logical manipulations. They contain them in small manageable modules (could be functions or classes).
I would say, they need to be respected not suppressed. They're in the language because they're useful.
Not quite. The variable 'result' is not converted. It stays 'uint32_t'. The expression to the right of the assignment operator ('=') is converted to match.
One key point in conversions: "length first, then signedness".
Before anything, 8-bit values in an expression are 'promoted' to 'int'. The unsigned char 'val', therefore, gets 'promoted' to 'int'. The unsigned 0xAA value (170). gets lengthened to 16 bits and because it is unsigned, it gets padded with zeroes: 0x00AA (170). But the promotion is to 'int', not 'unsigned int' so that 0x00AA is now a SIGNED integer (170).
Now we do the shift and get 0xAA00 (-22016), still in a signed integer.
The (16-bit) signed integer is about to be stored in a uint32_t ('result') so it has to be converted. First, it is lengthened. Because 'int' is a signed type it gets 'sign extended'. Bit 15 is a 1 so the 16-bit value gets padded to 32 bits with 1's: 0xFFFFAA00 (-22016). The resulting 'long' then becomes an 'unsigned long' to match the type of 'result' and stored in 'result'. The unsigned value is now 4294945280.