¿Por qué es ushort + ushort igual a int?

Previamente hoy intenté agregar dos ushorts y noté que tenía que devolver el resultado a ushort. Pensé que podría haberse convertido en un error (para evitar un posible desbordamiento involuntario), pero para mi sorpresa fue un int (System.Int32).

¿Hay alguna razón inteligente para esto o quizás porque int se ve como el tipo de entero ‘básico’?

Ejemplo:

ushort a = 1; ushort b = 2; ushort c = a + b; // <- "Cannot implicitly convert type 'int' to 'ushort'. An explicit conversion exists (are you missing a cast?)" uint d = a + b; // <- "Cannot implicitly convert type 'int' to 'uint'. An explicit conversion exists (are you missing a cast?)" int e = a + b; // <- Works! 

Editar: al igual que la respuesta de GregS, la especificación de C # dice que ambos operandos (en este ejemplo ‘a’ y ‘b’) deben convertirse a int. Estoy interesado en la razón subyacente de por qué esto es parte de la especificación: ¿por qué la especificación C # no permite operaciones directamente en los valores de ushort?

La respuesta simple y correcta es “porque la especificación del lenguaje C # lo dice”.

Claramente, no está satisfecho con esa respuesta y quiere saber “por qué lo dice”. Está buscando “fonts creíbles y / o oficiales”, eso será un poco difícil. Estas decisiones de diseño se tomaron hace mucho tiempo, 13 años son muchas vidas de perros en ingeniería de software. Fueron creados por los “veteranos”, como Eric Lippert los llama, pasaron a cosas más grandes y mejores y no publican respuestas aquí para proporcionar una fuente oficial.

Sin embargo, se puede inferir, a riesgo de ser simplemente creíble. Cualquier comstackdor administrado, como C #, tiene la restricción que necesita para generar código para la máquina virtual .NET. Las reglas para las cuales se describen cuidadosamente (y bastante legiblemente) en la especificación CLI. Es la especificación Ecma-335, puedes descargarla gratis desde aquí .

Pase a la Partición III, capítulos 3.1 y 3.2. Describen las dos instrucciones de IL disponibles para realizar una adición, add y add.ovf . Haga clic en el enlace a la Tabla 2, “Operaciones binarias numéricas”, describe qué operandos son permisibles para esas instrucciones de IL. Tenga en cuenta que solo hay unos pocos tipos allí enumerados. no existen los tipos de bytes y cortos, así como todos los tipos sin firmar. Solo int, long, IntPtr y floating point (flotante y doble) están permitidos. Con restricciones adicionales marcadas con una x, no puede agregar una int a una larga por ejemplo. Estas restricciones no son del todo artificiales, se basan en cosas que puede hacer de manera razonablemente eficiente en el hardware disponible.

Cualquier comstackdor gestionado tiene que tratar esto para generar IL válida. Eso no es difícil, simplemente convierta el ushort a un tipo de valor mayor que esté en la tabla, una conversión que siempre es válida. El comstackdor C # elige int, el siguiente tipo más grande que aparece en la tabla. O, en general, convierta cualquiera de los operandos al siguiente tipo de valor más grande para que ambos tengan el mismo tipo y cumplan con las restricciones de la tabla.

Ahora hay un nuevo problema, sin embargo, un problema que impulsa a los progtwigdores de C # bastante chiflados. El resultado de la adición es del tipo promocionado. En tu caso, eso será int. Así que agregar dos valores de ushort de, por ejemplo, 0x9000 y 0x9000 tiene un resultado int perfectamente válido: 0x12000. El problema es: ese es un valor que no encaja en un ushort. El valor se desbordó . Pero no se desbordó en el cálculo de IL, solo se desborda cuando el comstackdor intenta meterlo de nuevo en un ushort. 0x12000 se trunca a 0x2000. Un valor diferente desconcertante que solo tiene sentido cuando cuentas con 2 o 16 dedos, no con 10.

Notable es que la instrucción add.ovf no se ocupa de este problema. Es la instrucción a usar para generar automáticamente una excepción de desbordamiento. Pero no es así, el cálculo real en las entradas convertidas no se desbordó.

Aquí es donde la decisión de diseño real entra en juego. Los veteranos aparentemente decidieron que simplemente truncar el resultado int para ushort era una fábrica de errores. Ciertamente es Decidieron que debes reconocer que sabes que la adición puede desbordarse y que está bien si sucede. Hicieron su problema, sobre todo porque no sabían cómo hacerlo suyo y generar código eficiente. Tienes que lanzar. Sí, eso es enloquecedor, estoy seguro de que tampoco querías ese problema.

Muy notable es que los diseñadores de VB.NET tomaron una solución diferente al problema. De hecho lo convirtieron en su problema y no pasaron el dinero. Puede agregar dos UShorts y asignarlos a un UShort sin un molde. La diferencia es que el comstackdor VB.NET en realidad genera IL adicionales para verificar la condición de desbordamiento. Eso no es un código barato, hace que cada adición corta sea aproximadamente 3 veces más lenta. Pero, de lo contrario, la razón que explica por qué Microsoft mantiene dos idiomas que tienen capacidades muy similares.

Para resumir, está pagando un precio porque usa un tipo que no es muy bueno con las architectures de CPU modernas. Lo cual en sí mismo es una razón muy buena para usar uint en lugar de ushort. Obtener tracción de ushort es difícil, necesitará muchos de ellos antes de que el costo de manipularlos supere los ahorros de memoria. No solo por la especificación CLI limitada, un núcleo x86 necesita un ciclo de CPU extra para cargar un valor de 16 bits debido al byte prefijo del operando en el código máquina. En realidad, no estoy seguro de si ese sigue siendo el caso hoy en día, solía estar de vuelta cuando todavía prestaba atención a los ciclos de conteo. Un perro hace un año.


Tenga en cuenta que puede sentirse mejor con estos moldes feos y peligrosos al permitir que el comstackdor de C # genere el mismo código que el comstackdor de VB.NET genera. Entonces obtienes una OverflowException cuando el elenco resultó imprudente. Use Proyecto> Propiedades> pestaña Construir> botón Avanzado> marque la casilla “Comprobar el desbordamiento / desbordamiento aritmético”. Solo para la construcción de depuración. Por qué esta checkbox no está activada automáticamente por la plantilla del proyecto es otra pregunta muy desconcertante por cierto, una decisión que se tomó hace mucho tiempo.

 ushort x = 5, y = 12; 

La siguiente instrucción de asignación producirá un error de comstackción, porque la expresión aritmética en el lado derecho del operador de asignación se evalúa como int por defecto.

 ushort z = x + y; // Error: conversion from int to ushort 

http://msdn.microsoft.com/en-us/library/cbf1574z(v=vs.71).aspx

EDITAR:
En el caso de operaciones aritméticas en ushort, los operandos se convierten a un tipo que puede contener todos los valores. Para que ese desbordamiento se pueda evitar. Los operandos pueden cambiar en el orden de int, uint, long y ulong. Consulte la especificación del lenguaje C #. En este documento, vaya a la sección 4.1.5 Tipos integrales (alrededor de la página 80 en el documento Word). Aquí encontrarás:

Para los operadores binarios +, -, *, /,%, &, ^, |, ==,! =,>, < ,> = Y < =, los operandos se convierten a tipo T, donde T es el primero de int, uint, long y ulong que pueden representar completamente todos los valores posibles de ambos operandos . La operación se realiza usando la precisión de tipo T, y el tipo de resultado es T (o bool para los operadores relacionales). No está permitido que un operando sea de tipo largo y el otro sea de tipo ulong con los operadores binarios.

Eric Lipper ha declarado en una pregunta

La aritmética nunca se hace en pantalones cortos en C #. La aritmética se puede hacer en ints, uints, longs y ulongs, pero la aritmética nunca se realiza en cortocircuitos. Los cortos se promocionan a int y la aritmética se realiza en ints, porque como dije antes, la gran mayoría de los cálculos aritméticos se ajustan a un int. La gran mayoría no encaja en un corto. La aritmética breve es posiblemente más lenta en hardware moderno que está optimizado para ints, y la aritmética corta no ocupa menos espacio; se va a hacer en ints o longs en el chip.

Desde la especificación del lenguaje C #:

7.3.6.2 Promociones numéricas binarias La promoción numérica binaria se produce para los operandos de los operadores binarios predefinidos +, -, *, /,%, &, |, ^, ==,! =,>, < ,> = Y < = . La promoción numérica binaria convierte implícitamente ambos operandos a un tipo común que, en el caso de los operadores no relacionales, también se convierte en el tipo de resultado de la operación. La promoción numérica binaria consiste en aplicar las siguientes reglas, en el orden en que aparecen aquí:

· Si cualquier operando es de tipo decimal, el otro operando se convierte a tipo decimal, o se produce un error de tiempo de vinculación si el otro operando es de tipo float o double.

· De lo contrario, si cualquiera de los dos operandos es de tipo double, el otro operando se convierte a tipo double.

· De lo contrario, si cualquiera de los operandos es de tipo float, el otro operando se convierte a tipo float.

· De lo contrario, si cualquiera de los dos operandos es de tipo ulong, el otro operando se convierte en tipo ulong o se produce un error de tiempo de vinculación si el otro operando es de tipo sbyte, short, int o long.

· De lo contrario, si cualquiera de los dos operandos es de tipo largo, el otro operando se convierte en tipo largo.

· De lo contrario, si el operando es de tipo uint y el otro operando es de tipo sbyte, short o int, ambos operandos se convierten a tipo long.

· De lo contrario, si cualquiera de los operandos es del tipo uint, el otro operando se convierte a type uint.

· De lo contrario, ambos operandos se convierten a tipo int.

No hay razón por la cual se pretende. Esto es solo un efecto o aplicación de las reglas de resolución de sobrecarga que establecen que la primera sobrecarga para cuyos parámetros haya una conversión implícita que se ajuste a los argumentos, se utilizará la sobrecarga.

Esto se establece en la Especificación C #, sección 7.3.6 de la siguiente manera:

La promoción numérica no es un mecanismo distinto, sino más bien un efecto de aplicar resolución de sobrecarga a los operadores predefinidos.

Sigue ilustrando con un ejemplo:

Como ejemplo de promoción numérica, considere las implementaciones predefinidas del operador binario *:

operador int * (int x, int y);

uint operador * (uint x, uint y);

operador largo * (largo x, largo y);

operador ulong * (ulong x, ulong y);

operador de flotación * (float x, float y);

doble operador * (doble x, doble y);

operador decimal * (decimal x, decimal y);

Cuando las reglas de resolución de sobrecarga (§7.5.3) se aplican a este conjunto de operadores, el efecto es seleccionar el primero de los operadores para el que existen conversiones implícitas de los tipos de operandos. Por ejemplo, para la operación b * s, donde b es un byte ys es un short, la resolución de sobrecarga selecciona al operador * (int, int) como el mejor operador.

Tu pregunta es, de hecho, un poco complicada. La razón por la cual esta especificación es parte del lenguaje es … porque tomaron esa decisión cuando crearon el idioma. Sé que esto suena como una respuesta decepcionante, pero así es como es.


Sin embargo, la respuesta real probablemente implicaría muchas decisiones contextuales en el día en 1999-2000. Estoy seguro de que el equipo que hizo C # tuvo debates bastante sólidos sobre todos los detalles del idioma.

  • C # pretende ser un lenguaje de progtwigción simple, moderno, de propósito general orientado a objetos.
  • La portabilidad del código fuente es muy importante, como lo es la portabilidad del progtwigdor, especialmente para aquellos progtwigdores que ya están familiarizados con C y C ++.
  • El apoyo a la internacionalización es muy importante.

La cita anterior es de Wikipedia C #

Todos esos objectives de diseño podrían haber influido en su decisión. Por ejemplo, en el año 2000, la mayoría del sistema ya eran 32 bits nativos, por lo que podrían haber decidido limitar el número de variable más pequeño que eso, ya que se convertirá de todos modos en 32 bits cuando se realicen operaciones aritméticas. Que generalmente es más lento

En ese punto, podrías preguntarme; si hay una conversión implícita en esos tipos, ¿por qué los incluyeron de todos modos? Bueno, uno de sus objectives de diseño, como se citó anteriormente, es la portabilidad.

Por lo tanto, si necesita escribir un contenedor C # alrededor de un antiguo progtwig C o C ++, puede necesitar ese tipo para almacenar algunos valores. En ese caso, esos tipos son bastante útiles.

Esa es una decisión que Java no hizo. Por ejemplo, si escribe un progtwig Java que interactúa con un progtwig C ++ de qué manera se reciben los valores de ushort, bien Java solo tiene short (que están firmados) por lo que no puede asignar fácilmente uno a otro y esperar valores correctos.

Te dejo apostar, el próximo tipo disponible que podría recibir tal valor en Java es int (32 bits por supuesto). Usted acaba de duplicar su memoria aquí. Lo cual podría no ser un gran problema, en su lugar, debes instanciar una matriz de 100 000 elementos.

De hecho, debemos recordar que esas decisiones se tomaron mirando el pasado y el futuro a fin de proporcionar una transferencia fluida de uno a otro.

Pero ahora siento que estoy divergiendo de la pregunta inicial.


Entonces, tu pregunta es buena y espero poder brindarte algunas respuestas, aunque si sé que probablemente no sea lo que querías escuchar.

Si lo desea, puede leer más acerca de la especificación de C #, enlaces a continuación. Existe alguna documentación interesante que podría ser interesante para usted.

Tipos integrales

Los operadores marcados y no revisados

Tabla de conversiones numéricas implícitas

Por cierto, creo que probablemente deberías recompensar a Habib-osu por ello, ya que proporcionó una respuesta bastante buena a la pregunta inicial con un enlace adecuado. 🙂

Saludos