1

I actually test the casting behavior in C# in unchecked context. Like the documentation said, in unchecked context, the cast always succeed. But sometimes, in particular cases, the cast from one specific type to another type give unexpected result.

For example, i tested three "double to sbyte" casts :

var firstCast = (sbyte) -129.83297462979882752;          // Result : 127.
var secondCast = (sbyte) -65324678217.74282742874973267; // Result : 0.
var thirdCast = (sbyte) -65324678216.74282742874973267;  // Result : 0.

Just to be clear, the difference between the second and the third double is just 1 (secondDouble - firstDouble = 1). In this case, results of casting seem to always be 0 for any "big" double value.

My question is : why the second and the third casts result in 0 ? I searched for an answer in the C# documentation, but i did not find any.

I tested the above with the .Net Framework 4.7.2.

  • 5
    sbyte only has a range from -128 to 127. I wouldn't expect either of these casts to make any real sense or have a meaningful result, and the only reason you don't get an exception is the unchecked context. Also, both of those literals already have more precision than would fit in a double. – Joel Coehoorn Jan 15 '20 at 16:32
  • Not sure if c# acts like that, but could this be because it acts as a CircularList ? As mentioned by Joel, the min value is -128. so when casting -129 it is reaching -128 + 1 => 127 (max value). -128 is linked to 127 – WilliamW Jan 15 '20 at 16:34
  • 2
    Also, I get -73, not 0. https://dotnetfiddle.net/eYrRPw – Joel Coehoorn Jan 15 '20 at 16:36
  • Floating point number are stores as a mantissa and an exponent. A single is 4 bytes and the double is 8 bytes. So when you are casting to one byte the compiler is taking only the first byte or the 4/8 byte object. – jdweng Jan 15 '20 at 16:38
  • Yes. if you try -130. You are reaching -128 + 2 => 127 - 1 => 126. So I suspect the reason is given on my comment – WilliamW Jan 15 '20 at 16:38
  • 4
    My guess is that it first casts to int & then it takes the low sbyte. In your second example - a cast to int results in 0x80000000, resulting in 0. If you first cast to long, then to sbyte - you get 0xB6 - "(sbyte)(long) -65324678216.74282742874973267" - result of cast to long is 0xFFFFFFF0CA5883B6 – PaulF Jan 15 '20 at 16:39
  • It wouldn't make any sense to double cast from float -> int -> byte. Id question the sanity of the .NET team if they did that. –  Jan 15 '20 at 16:39
  • @JoelCoehoorn -73 is the result i expected to get, but in my Visual Studio, for any reason, i get 0. – TheRealDuBoySem Jan 15 '20 at 16:45
  • Updated fiddle with the additional cast added to the question. Note I explicitly set fiddle to use .Net 4.7.2 rather than roslyn or core, to match the question. https://dotnetfiddle.net/DVOWNN – Joel Coehoorn Jan 15 '20 at 16:46
  • @jdweng that's not at all how casting works. It does not take a subset of the byte representation of numbers, it looks at the _numeric_ representation thereof. Look at https://www.ideone.com/qeG7Cx and https://www.ideone.com/ubvSAg – CodeCaster Jan 15 '20 at 17:19

1 Answers1

3

According to the C# language specification,

For a conversion from float or double to an integral type, the processing depends on the overflow checking context in which the conversion takes place:

Without using the checked or unchecked operators, by default the overflow checking context is unchecked, so we look at:

In an unchecked context, the conversion always succeeds, and proceeds as follows.

  • If the value of the operand is NaN or infinite, the result of the conversion is an unspecified value of the destination type.

  • Otherwise, the source operand is rounded towards zero to the nearest integral value. If this integral value is within the range of the destination type then this value is the result of the conversion.

  • Otherwise, the result of the conversion is an unspecified value of the destination type.

Here, the values are neither NaN nor infinite. When rounded towards zero, they are not in the valid range of sbyte, which is -128 to 127, therefore the last bullet point applies, which means that the result of such a cast is unspecified.

In other words, the result of this cast depends on which compiler you are using. Different compilers could do different things, and they will still be called C# compilers. It is likely that whatever compiler that you are using just thought it'd be a better idea to return 0 for the conversion when the value to convert is very far away the lower/upper bound.

Community
  • 1
  • 1
Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • Interesting, so i have to check which C# compiler i use. – TheRealDuBoySem Jan 15 '20 at 16:49
  • @TheRealDuBoySem You said ".Net Framework 4.7.2", so I _think_ it's `csc`. – Sweeper Jan 15 '20 at 16:50
  • Probably VS and LINQPad6 (both gives 0) compilers are following @PaulF theory and rounding to `int` before casting to `sbyte`. That gives 0 for second and third cases. Compilers that give -73 are rounding to `long` before casting to `sbyte`. – Magnetron Jan 15 '20 at 16:52
  • @TheRealDuBoySem no, you should not cast at all, but uses instead a proper method with defined behaviour. – Magnetron Jan 15 '20 at 16:56
  • @Magnetron Wait, is OP implying that he's gonna keep using a cast? I thought he's just checking which compiler he uses out of curiosity... Seems like I misunderstood. – Sweeper Jan 15 '20 at 16:58
  • @Sweeper yeah, I'm not sure which interpretation is right, but better safe than sorry, so I left the comment. – Magnetron Jan 15 '20 at 17:02
  • @Magnetron I already had one that gives me the expected result, but i was perplexed about why my method did not always give the cast result. But thanks all for your help! – TheRealDuBoySem Jan 15 '20 at 17:04