c# boolean bitwise-operators logical-operators pex

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:

  1. ¿Significa esto que x & y y x && y definitivamente no tienen los mismos resultados para todos los argumentos bool posibles? (¿O podría ser algo buggy en Pex?)
  2. ¿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 es true si tanto x como y son true . De lo contrario, el resultado es false .

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:

  1. Actualmente, es posible que x & y y x && y tengan diferentes valores. Sin embargo, este comportamiento viola la especificación de C #.
  2. Actualmente, puede usar Boolean.Equals (como se muestra arriba) para diferenciar entre valores true . Sin embargo, este comportamiento viola la especificación CLI de Boolean.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 :