sparkyShorts
sparkyShorts

Reputation: 630

Why Do Bytes Carryover?

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?

Upvotes: 3

Views: 368

Answers (5)

Maksym Shysha
Maksym Shysha

Reputation: 1

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.

Upvotes: 0

Lukazoid
Lukazoid

Reputation: 19426

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.

Upvotes: 6

dan04
dan04

Reputation: 91209

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.

Upvotes: 2

kajacx
kajacx

Reputation: 12949

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;
 }

Upvotes: 2

Eric J.
Eric J.

Reputation: 150158

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.

Upvotes: 8

Related Questions