I think this looks like a bug in the C# compiler.
Consider this code (inside a method):
const long dividend = long.MinValue; const long divisor = -1L; Console.WriteLine(dividend % divisor);
It compiles with no errors (or warnings). Seems like a bug. When run, prints 0
on console.
Then without the const
, the code:
long dividend = long.MinValue; long divisor = -1L; Console.WriteLine(dividend % divisor);
When this is run, it correctly results in an OverflowException
being thrown.
The C# Language Specification mentions this case specifically and says a System.OverflowException
shall be thrown. It does not depend on the context checked
or unchecked
it seems (also the bug with the compile-time constant operands to the remainder operator is the same with checked
and unchecked
).
Same bug happens with int
(System.Int32
), not just long
(System.Int64
).
For comparison, the compiler handles dividend / divisor
with const
operands much better than dividend % divisor
.
My questions:
Am I right this is a bug? If yes, is it a well-known bug that they do not wish to fix (because of backwards compatibility, even if it is rather silly to use % -1
with a compile-time constant -1
)? Or should we report it so that they can fix it in upcoming versions of the C# compiler?
This corner-case is very specifically addressed in the compiler. Most relevant comments and code in the Roslyn source:
// Although remainder and division always overflow at runtime with arguments int.MinValue/long.MinValue and -1 // (regardless of checked context) the constant folding behavior is different. // Remainder never overflows at compile time while division does. newValue = FoldNeverOverflowBinaryOperators(kind, valueLeft, valueRight);
And:
// MinValue % -1 always overflows at runtime but never at compile time case BinaryOperatorKind.IntRemainder: return (valueRight.Int32Value != -1) ? valueLeft.Int32Value % valueRight.Int32Value : 0; case BinaryOperatorKind.LongRemainder: return (valueRight.Int64Value != -1) ? valueLeft.Int64Value % valueRight.Int64Value : 0;
Also the behavior of the legacy C++ version of compiler, going all the way back to version 1. From the SSCLI v1.0 distribution, clr/src/csharp/sccomp/fncbind.cpp source file:
case EK_MOD: // if we don't check this, then 0x80000000 % -1 will cause an exception... if (d2 == -1) { result = 0; } else { result = d1 % d2; } break;
So conclusion to draw that this was not overlooked or forgotten about, at least by the programmers that worked on the compiler, it could perhaps be qualified as insufficiently precise language in the C# language specification. More about the runtime trouble caused by this killer poke in this post.
I think it's not a bug; it's rather how C# compiler computes %
(It's a guess). It seems that C# compiler first computes %
for positive numbers, then applies the sign. Having Abs(long.MinValue + 1) == Abs(long.MaxValue)
if we write:
static long dividend = long.MinValue + 1; static long divisor = -1L; Console.WriteLine(dividend % divisor);
Now we will see 0
as the answer which is correct because now Abs(dividend) == Abs(long.MaxValue)
which is in range.
Why it works when we declare it as a const
value then? (Again a guess) It seems that C# compiler actually computes the expression at compile time and does not considers the type of the constant and act on it as a BigInteger
or something (bug?). Because if we declare a function like:
static long Compute(long l1, long l2) { return l1 % l2; }
And call Console.WriteLine(Compute(dividend, divisor));
we will get the same exception. And again, if we declare the constant like this:
const long dividend = long.MinValue + 1;
We would not get the exception.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With