comparing a byte to itself + a value that would exceed 255

I am working on a home lighting project that is working quite well so far, but while going over the code with a fine comb I noticed something that might cause mathematical issues in certain situations and I'm not sure how the comparison is actually handled...

I have numerous values that will never actually stray outside of the range 0-255 so I've been using bytes. Additionally, I am using a library (FastLED) where I can read or write an R, G, or B value that is a byte (0-255). I have written a function that will increase or decrease one of these colour values at a time by a desired amount in an attempt to reach a target value (the result is smooth fading). I have a check that happens before the adjustment amount is added/subtracted to prevent it from overshooting the target, and instead it will "snap" to the target.

byte adjustedColourValue(byte currentColourValue, byte targetColourValue, byte adjustmentAmount){
  byte adjustedColourValue = currentColourValue;
  if (currentColourValue < targetColourValue){
    if ((currentColourValue + adjustmentAmount) > targetColourValue){
      adjustedColourValue = targetColourValue;
    } else {
      adjustedColourValue = currentColourValue + adjustmentAmount;
    }
  }
  if (currentColourValue > targetColourValue){
    if ((currentColourValue - adjustmentAmount) < targetColourValue){
      adjustedColourValue = targetColourValue;
    } else {
      adjustedColourValue = currentColourValue - adjustmentAmount;
    }
  }
  return adjustedColourValue;
}

An example of my concern is the following line when my target value is 255:

if ((currentColourValue + adjustmentAmount) > targetColourValue){...

Since all values involved are bytes, if currentColourValue is 252 at this moment, and adjustmentAmount is 4, then currentColourValue + adjustmentAmount equals 256, exceeding the value that a byte can hold... so my question is: how is this situation handled? is there some kind of automatic casting going on? could this get me in trouble? it seems like I'm taking a byte and exceeding its range...
Furthermore, is there anything noteworthy about the actual comparing of a value like 256 to a byte? what if you compared 6520 to a byte? will you get a reliable result (that it is larger)? or is there an issue because you are essentially comparing two different variable types...

In my program, the currentColourValue starts at 0 and one of my adjustment values is 4... so my example above is occurring in the real world and I don't see any physical problems with the light flickering/cycling/etc but I want to be sure that there isn't something weird going on with the code that might cause me grief in the future...

Well, you could write a short test program and see what happens. Or, you could play it on the safe side:

if (((int16_t) currentColourValue + adjustmentAmount) > targetColourValue){...

The addition is performed after the operands are promoted to int, and the comparison is also performed after the right-hand side is promoted to int, so it should work without any sort of overflow issues.

Doesn't fastLED have built in fading functions? Also the colour 256 (if it existed) would be the same as the colour 0 (red) if you are using HSV colours. See this. So you probably don't even need this as you can take advantage of this wrapping around to 0.

Thanks for the quick replies here :slight_smile:

christop:
The addition is performed after the operands are promoted to int, and the comparison is also performed after the right-hand side is promoted to int, so it should work without any sort of overflow issues.

If this is the case, that's great (and would match the real-world behaviour I'm experiencing). Do you have any reference for this information, like a link to some kind of documentation? Not to question your statement, but I like to be certain about things (hence making this post in the first place). Sounds like you're saying that it's part of the language and/or compiler... a built-in casting when performing math and comparisons... and just to really dig into it: being promoted to an int means that it is able to be a negative value as well right? so when my code fades back down, the left side of the comparison could be a negative number and that would be totally fine as well...

Metallor:
Doesn't fastLED have built in fading functions? Also the colour 256 (if it existed) would be the same as the colour 0 (red) if you are using HSV colours. See this. So you probably don't even need this as you can take advantage of this wrapping around to 0.

I'm trying to do my own program from scratch and only using FastLED as means to communicate with my LED drivers. I'm using RGB (where each colour is represented by a value from 0 to 255) so if there was some kind of rollover/wraparound to 0 then that colour would go completely off. This is not happening so it's not a concern in this case. What I'm worried about is reading/writing to memory outside of the actual byte that I want to be using, and thus getting weird results... like affecting some other part of the program (from writing) or getting an undesired colour value (from reading).

Nothing in your code will cause any memory to be corrupted. It's when you start trying to write to memory outside the bounds of an array for example that you will run into issues.
If you want to check if your values have overflowed or not, before updating you could save your previous value, add your increment, then check if your new value is below the previous value. If this is the case (assuming a positive increment) that check would indicate you have overflowed and you could set the value to 255.

Metallor:
Nothing in your code will cause any memory to be corrupted. It's when you start trying to write to memory outside the bounds of an array for example that you will run into issues.
If you want to check if your values have overflowed or not, before updating you could save your previous value, add your increment, then check if your new value is below the previous value. If this is the case (assuming a positive increment) that check would indicate you have overflowed and you could set the value to 255.

Is the overflow that you're referring to (256 is actually 0) due to the value being truncated? meaning that it drops the 9th bit, leaving all zeros in the other 8 bits? This makes sense to me, along with your comment on arrays, because writing outside of the memory that you've allocated for an array means that you're writing to an address that you shouldn't be... but trying to write a number that is larger than 255 to a byte wouldn't cause problems if it is truncated before the actual writing-to-memory takes place.

Your comment about checking the value for overflow is essentially already handled by my function, because if it determines that the addition will exceed 255, it doesn't do the addition, it just sets the value to 255. And as long as my bytes are being promoted to int then I want to be asking if it is greater than 255, not less than. Again, this seems to be working correctly, I just want to feel certain that the computations are doing what I think they're doing.

Also, I found this documentation that talks about operand promotion to int, which corroborates christop's post. It's talking about C though... isn't Arduino a derivative of C++? Or is this standard inherited (C to C++ to Arduino):
https://wiki.sei.cmu.edu/confluence/display/c/INT02-C.+Understand+integer+conversion+rules

tinynest:
Also, I found this documentation that talks about operand promotion to int, which corroborates christop's post. It's talking about C though... isn't Arduino a derivative of C++? Or is this standard inherited (C to C++ to Arduino):
INT02-C. Understand integer conversion rules - SEI CERT C Coding Standard - Confluence

That's actually the same page I was about to link to! :slight_smile:

Yes, C++ and C are practically identical in this area, as C++ effectively "inherited" its conversion rules from C (except C++ has more conversion rules because it supports more types than C, but for integer types they're the same).

Awesome, ok. Thanks for discussing this with me.

So as far as I understand this now:

Variables that are smaller (in memory size) than int get promoted to int when math and comparisons are being performed, and they remain int throughout that process (including when evaluating whether to return true/false in a comparison)... but if a value that is the result of math done on promoted variables needs to be written to a variable that is smaller (in memory size) than int, it is truncated before being written.

Anything wrong with this statement? ^^

void setup(){
  byte a = 10;
  byte b = 252;
  byte c = 255;
  if(b + a > c){
    Serial.print("something became an int somewhere or some other magic");
  }
  else{
    Serial.print("nope, they all remained bytes and the addition overflowed");
  }
}
void loop(){}

You could try something like this to find out.