c# - ¿Cómo puede ser falso “x & y” cuando tanto x como y son verdaderos?
boolean bitwise-operators (3)
Contexto:
Estoy aprendiendo C # y he estado jugando en el sitio de Pex por diversión . El sitio lo desafía a que vuelva a implementar un algoritmo secreto, escribiendo código en el sitio y examinando cómo las entradas y salidas difieren entre su implementación y la implementación secreta.
Problema:
De todos modos, me quedé atascado en un duelo de código básico llamado XAndY .
Por el nombre parecía obvio que la respuesta era simplemente:
public static bool Puzzle(bool x, bool y)
{
return x && y;
}
Sin embargo, esto fue incorrecto y Pex me informó que las siguientes entradas produjeron un resultado diferente al de la implementación secreta:
Entrada:
x: verdadero y: verdadero (0x02)
Salida:
mi implementación: verdadera (0x02)
implementación secreta: falso
Desajuste Su método de rompecabezas produjo el resultado incorrecto.
Código: Puzzle (verdadero, PexSafeHelpers.ByteToBoolean ((byte) 2));
Después de mucha confusión al tratar de comparar diferentes tipos de verdad, me di cuenta de que la implementación que Pex estaba buscando en realidad estaba usando un bit AND:
return x & y;
Preguntas:
Pensé que, por razones tanto semánticas como de cortocircuito, deberías usar lógico &&
para comparar valores booleanos, pero independientemente:
- ¿Significa esto que
x & y
yx && y
definitivamente no tienen los mismos resultados para todos los argumentos bool posibles? (¿O podría ser algo buggy en Pex?) - ¿Significa esto que puede diferenciar entre diferentes valores de bool
true
en C #? ¿Si es así, cómo?
El rompecabezas está explotando lo que, en mi opinión, es un error en el compilador de C #. (El error afecta a VB.NET también.)
En la especificación C # 5.0, §4.1.8 dice que "Los valores posibles de tipo bool
son true
y false
", y §7.11.3 dice que el operator &(bool x, bool y)
es un operador lógico :
El resultado de
x & y
estrue
si tantox
comoy
sontrue
. De lo contrario, el resultado esfalse
.
Obviamente, es una violación de la especificación de true & true
para dar false
. ¿Que esta pasando?
En el tiempo de ejecución, un bool
está representado por un entero de 1 byte. El compilador de C # usa 0 para representar false
y 1 para representar true
. Para implementar el operador &
, el compilador C # emite una instrucción AND
bit en el IL generado. A primera vista, esto parece estar bien: las operaciones AND
bits que involucran 0 y 1 corresponden exactamente a las operaciones AND
lógicas que involucran false
y true
.
Sin embargo, el §III.1.1.2 de la especificación de la CLI permite explícitamente que un bool
sea representado por un número entero distinto de 0 o 1:
Un tipo booleano CLI ocupa 1 byte en la memoria. Un patrón de bits de todos los ceros denota un valor de falso. Un patrón de bits con uno o más bits establecidos (análogo a un entero que no es cero) denota un valor de verdadero.
Al ir más allá del alcance de C #, es posible, y perfectamente legal, crear un bool
cuyo valor es, por ejemplo, 2, lo que provoca &
comporta de manera inesperada. Esto es lo que hace el sitio de Pex.
Aquí hay una demostración:
using System;
using System.Reflection.Emit;
class Program
{
static void Main()
{
DynamicMethod method =
new DynamicMethod("ByteToBoolean", typeof(bool), new[] { typeof(byte) });
ILGenerator il = method.GetILGenerator();
il.Emit(OpCodes.Ldarg_0); // Load the byte argument...
il.Emit(OpCodes.Ret); // and "cast" it directly to bool.
var byteToBoolean =
(Func<byte, bool>)method.CreateDelegate(typeof(Func<byte, bool>));
bool x = true;
bool y = byteToBoolean(2);
Console.WriteLine(x); // True
Console.WriteLine(y); // True
Console.WriteLine(x && y); // True
Console.WriteLine(x & y); // False (!) because 1 & 2 == 0
Console.WriteLine(y.Equals(false)); // False
Console.WriteLine(y.Equals(true)); // False (!) because 2 != 1
}
}
Así que las respuestas a tus preguntas son:
- Actualmente, es posible que
x & y
yx && y
tengan diferentes valores. Sin embargo, este comportamiento viola la especificación de C #. - Actualmente, puede usar
Boolean.Equals
(como se muestra arriba) para diferenciar entre valorestrue
. Sin embargo, este comportamiento viola la especificación CLI deBoolean.Equals
.
Noté que este problema se ha planteado en el grupo de compiladores de Roslyn. Más discusión .
Tenía la siguiente resolución:
Esto es efectivamente por diseño y puede encontrar el documento que detalla aquí: https://github.com/dotnet/roslyn/blob/master/docs/compilers/Boolean%20Representation.md
Puedes ver más detalles y conversaciones anteriores sobre esto aquí: # 24652
El documento señalado declara:
Representación de valores booleanos
Los compiladores de C # y VB representan los valores booleanos (booleanos) verdaderos (verdaderos) y falsos (falsos) con los valores de byte único 1 y 0, respectivamente, y suponen que cualquier valor booleano con el que estén trabajando está restringido a ser representado por estos dos valores subyacentes. La especificación ECMA 335 CLI permite que un valor booleano "verdadero" se represente por cualquier valor distinto de cero. Si usa valores booleanos que tienen una representación subyacente distinta de 0 o 1, puede obtener resultados inesperados. Esto puede ocurrir en el código inseguro en C #, o al interactuar con un idioma que permite otros valores. Para evitar estos resultados inesperados, es responsabilidad del programador normalizar los valores entrantes .
(Todas las cursivas son mi propio énfasis)
&
en C # no es un operador bit a bit, asumiendo que los valores de entrada son valores Boolean
. Está sobrecargado. Hay dos implementaciones totalmente separadas del operador. Un operador booleano lógico sin cortocircuito si las entradas son booleanas, y un bit a modo de AND si los valores son valores no booleanos.
En el código que ha mostrado, la entrada es una variable booleana. No es un valor numérico, no es una expresión que se resuelve como un valor booleano (que puede tener efectos secundarios), o cualquier otra cosa.
Cuando la entrada es dos variables booleanas, nunca habrá diferencias en la salida entre &
y &&
. La única manera de tener una diferencia observable entre estos dos es tener una expresión booleana que sea más compleja que solo resolver una variable a su valor, o alguna entrada no booleana.
Si los operandos podrían ser de otro tipo que no sea bool
entonces es bastante trivial proporcionar un tipo que tenga diferentes resultados para cada operador, como un tipo súper medio que reemplaza al operador true
en una manera inconsistente con su conversión implícita a bool
: