22

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?

Charles
  • 50,943
  • 13
  • 104
  • 142
Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
  • Mentioning @EricLippert might draw in the right crowd for this question :) – Morten Mertner Aug 18 '13 at 13:14
  • @Morten, at this point, he might just gaze bemusedly from his perch at Coverity. ;) – Kirk Woll Aug 18 '13 at 13:55
  • I think you should put a bounty on this as it irritates me why this is happening. The specs say that any constant expression that may throw a run-time exception should cause a compile-time error at compilation !! – Ibrahim Najjar Aug 19 '13 at 13:41
  • @Sniffer It is not open for bounties yet. There is still the chance some authoritative answerer will show up. But if not, anyone is welcome to set a bounty :-) – Jeppe Stig Nielsen Aug 19 '13 at 16:56
  • I don't think you can modulo and have a remainder when dividing by a negative since it rounds towards zero. (chip dependent possibly based on correspondence with peers). Negative divisors, oi. Curious if this helps! https://math.stackexchange.com/questions/519845/modulo-of-a-negative-number/519856 –  May 25 '17 at 15:10

2 Answers2

19

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.

Community
  • 1
  • 1
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
4

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.

Kaveh Shahbazian
  • 13,088
  • 13
  • 80
  • 139
  • 1
    I did know all of that already. Note that the spec says: _The result of `x % y` is the value produced by `x – (x / y) * y`. If `y` is zero, a `System.DivideByZeroException` is thrown. ↵↵ If the left operand is the smallest `int` or `long` value and the right operand is `-1`, a `System.OverflowException` is thrown. [...]_ It is obvious from your observations (and mine) that the compiler does not follow the spec when the remainder is computed at compile-time. The runtime does follow the spec. – Jeppe Stig Nielsen Aug 18 '13 at 19:45
  • My apologies; I did not read the spec. Yes; I saw it now in my answer too at "acts on it as a BigInteger or something (bug?)". You are correct. – Kaveh Shahbazian Aug 18 '13 at 19:59