3

I have been playing with some byte arrays recently (dealing with grayscale images). A byte can have values 0-255. I was modifying the bytes, and came across a situation where the value I was assigning to the byte was outside the bounds of the byte. It was doing unexpected things to the images I was playing with.

I wrote a test and learned that the byte carries over. Example:

private static int SetByte(int y)
{
    return y;
}
.....
byte x = (byte) SetByte(-4);
Console.WriteLine(x);
//output is 252

There is a carryover! This happens when we go the other way around as well.

byte x = (byte) SetByte(259);
Console.WriteLine(x);
//output is 3

I would have expected it to set it to 255 in the first situation and 0 in the second. What is the purpose of this carry over? Is it just due to the fact that I'm casting this integer assignment? When is this useful in the real-world?

sparkyShorts
  • 630
  • 9
  • 28

5 Answers5

8
byte x = (byte) SetByte(259);
Console.WriteLine(x);
//output is 3

The cast of the result of SetByte is applying modulo 256 to your integer input, effectively dropping bits that are outside the range of a byte.

259 % 256 = 3

Why: The implementers choose to only consider the 8 least significant bits, ignoring the rest.

Sumner Evans
  • 8,951
  • 5
  • 30
  • 47
Eric J.
  • 147,927
  • 63
  • 340
  • 553
  • That makes sense and I understand that, but that doesn't answer the purpose for doing so. Why not set it to 255? Or do I just need to accept it for what it is and not ask 'Why'? :D – sparkyShorts Feb 02 '16 at 23:59
  • 2
    "Why" is indeed the better question. The implementers choose to only consider the 8 least significant bits, ignoring the rest. – Eric J. Feb 03 '16 at 00:00
  • So it sounds like it may vary on the language, then. – sparkyShorts Feb 03 '16 at 00:02
  • 1
    If it was set to "255 or 0" I would expect we'd see a question on SO why that was the case instead of a carryover :) – jamesSampica Feb 03 '16 at 00:08
6

When compiling C# you can specify whether the assembly should be compiled in checked or unchecked mode (unchecked is default). You are also able to make certain parts of code explicit via the use of the checked or unchecked keywords.

You are currently using unchecked mode which ignores arithmetic overflow and truncates the value. The checked mode will check for possible overflows and throw if they are encountered.

Try the following:

int y = 259;
byte x = checked((byte)y);

And you will see it throws an OverflowException.

The reason why the behaviour in unchecked mode is to truncate rather than clamp is largely for performance reasons, every unchecked cast would require conditional logic to clamp the value when the majority of the time it is unnecessary and can be done manually.

Another reason is that clamping would involve a loss of data which may not be desirable. I don't condone code such as the following but have seen it (see this answer):

int input = 259;
var firstByte = (byte)input;
var secondByte = (byte)(input >> 8);

int reconstructed = (int)firstByte + (secondByte << 8);

Assert.AreEqual(reconstructed, input);

If firstByte came out as anything other than 3 this would not work at all.

One of the places I most commonly rely upon numeric carry over is when implementing GetHashCode(), see this answer to What is the best algorithm for an overridden System.Object.GetHashCode by Jon Skeet. It would be a nightmare to implement GetHashCode decently if overflowing meant we were constrained to Int32.MaxValue.

Community
  • 1
  • 1
Lukazoid
  • 19,016
  • 3
  • 62
  • 85
2

The method SetByte is irrelevant, simply casting (byte) 259 will also result in 3, since downcasting integral types is implemented as cutting of bytes.

You can create a custom clamp function:

 public static byte Clamp(int n) {
     if(n <= 0) return 0;
     if(n >= 256) return 255;
     return (byte) n;
 }
kajacx
  • 12,361
  • 5
  • 43
  • 70
  • That's almost exactly what I've done to handle the values being outside the range of the byte. – sparkyShorts Feb 03 '16 at 00:12
  • 4
    If you wonder why this behaviour isn't default when casting, it's because simply cuting off bytes is very fast (one or few instructions) while clamping needs if-else, which requires slow-ish conditinal jump. – kajacx Feb 03 '16 at 00:16
2

Doing arithmetic modulo 2^n makes it possible for overflow errors in different directions to cancel each other out.

byte under = -12; // = 244
byte over  = (byte) 260; // = 4
byte total = under + over;
Console.WriteLine(total); // prints 248, as intended

If .NET instead had overflows saturate, then the above program would print the incorrect answer 255.

dan04
  • 87,747
  • 23
  • 163
  • 198
  • 1
    Nice point and nice answer, though note `byte over = 260` does not compile. You would have to assign by casting a type capable of holding the value 260. – Eric J. Feb 03 '16 at 00:15
0

The bounds control is not active for a case with direct type cast (when using (byte)) to avoid performance reducing.

FYI, result of most operations with operands of byte is integer, excluding the bit operations. Use Convert.ToByte() and you will get an Overflow Exception and you may handle it by assigning the 255 to your target.

Or you may create a fuction to do this check, as mentioned by another guy below. If the perfomanse is a key, try to add attribute [MethodImpl(MethodImplOptions.AggressiveInlining)] to that fuction.

Tom Fuller
  • 5,291
  • 7
  • 33
  • 42