c# - ¿Por qué el compilador evalúa el resto MinValue%-1 diferente al tiempo de ejecución?
operators modulus (2)
Creo que esto parece un error en el compilador de C #.
Considere este código (dentro de un método):
const long dividend = long.MinValue;
const long divisor = -1L;
Console.WriteLine(dividend % divisor);
Se compila sin errores (o advertencias). Parece un error. Cuando se ejecuta, imprime 0
en la consola.
Luego, sin la const
, el código:
long dividend = long.MinValue;
long divisor = -1L;
Console.WriteLine(dividend % divisor);
Cuando esto se ejecuta, correctamente se produce una OverflowException
.
La especificación de lenguaje C # menciona este caso específicamente y dice que se System.OverflowException
una System.OverflowException
. No depende del contexto checked
o unchecked
(también el error con los operandos de constantes de tiempo de compilación para el operador restante es el mismo con el checked
y unchecked
).
El mismo error ocurre con int
( System.Int32
), no solo long
( System.Int64
).
Para comparación, el compilador maneja dividend / divisor
con operandos const
mucho mejor que dividend % divisor
.
Mis preguntas:
Estoy en lo cierto esto es un error? En caso afirmativo, ¿es un error conocido que no desean solucionar (debido a la compatibilidad con versiones anteriores, incluso si es bastante tonto usar % -1
con una constante de tiempo de compilación -1
)? ¿O deberíamos informarlo para que puedan solucionarlo en las próximas versiones del compilador de C #?
Creo que no es un error; es más bien cómo el compilador C # calcula el %
(es una conjetura). Parece que el compilador C # primero calcula el %
para números positivos, luego aplica el signo. Tener Abs(long.MinValue + 1) == Abs(long.MaxValue)
si escribimos:
static long dividend = long.MinValue + 1;
static long divisor = -1L;
Console.WriteLine(dividend % divisor);
Ahora veremos 0
como la respuesta que es correcta porque ahora Abs(dividend) == Abs(long.MaxValue)
que está en el rango.
¿Por qué funciona cuando lo declaramos como un valor const
? (Una vez más, una conjetura) Parece que el compilador de C # en realidad calcula la expresión en el momento de la compilación y no considera el tipo de la constante y actúa como BigInteger
o algo (bug?). Porque si declaramos una función como:
static long Compute(long l1, long l2)
{
return l1 % l2;
}
Y llame a Console.WriteLine(Compute(dividend, divisor));
Obtendremos la misma excepción. Y de nuevo, si declaramos la constante así:
const long dividend = long.MinValue + 1;
No obtendríamos la excepción.
Este caso de esquina se trata muy específicamente en el compilador. Comentarios y código más relevantes en la fuente de Roslyn :
// 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);
Y:
// 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;
También el comportamiento de la versión heredada de C ++ del compilador, que se remonta a la versión 1. Desde la distribución SSCLI v1.0, clr / src / csharp / sccomp / fncbind.cpp archivo de origen:
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;
De modo que, para concluir, no se pasó por alto ni se olvidó de esto, al menos por parte de los programadores que trabajaron en el compilador, tal vez podría calificarse como lenguaje insuficientemente preciso en la especificación del lenguaje C #. Más información sobre los problemas de tiempo de ejecución causados por este golpe asesino en este post .