Mirando este código C #:
byte x = 1;
byte y = 2;
byte z = x + y; // ERROR: Cannot implicitly convert type 'int' to 'byte'
El resultado de cualquier matemática realizada en byte
(o short
) tipos se devuelve implícitamente a un entero. La solución es devolver explícitamente el resultado a un byte:
byte z = (byte)(x + y); // this works
Lo que me pregunto es por qué. ¿Es arquitectónico? ¿Filosófico?
Tenemos:
int
+int
=int
long
+long
=long
float
+float
=float
double
+double
=double
Entonces por qué no:
byte
+byte
=byte
short
+short
=short
?
Un poco de antecedentes: estoy realizando una larga lista de cálculos sobre "números pequeños" (es decir, <8) y almacenando los resultados intermedios en una gran matriz. Usar una matriz de bytes (en lugar de una matriz int) es más rápido (debido a los éxitos de caché). Pero los extensos conjuntos de bytes distribuidos a través del código lo hacen mucho más ilegible.
c#
type-conversion
Robert Cartaino
fuente
fuente
byte1 | byte2
no los trata en absoluto como números. Esto los trata con precisión como patrones de bits. Entiendo su punto de vista, pero sucede que cada vez que hice aritmética en bytes en C #, en realidad los estaba tratando como bits, no como números, y este comportamiento siempre está en el camino.Respuestas:
La tercera línea de su fragmento de código:
en realidad significa
Por lo tanto, no hay operación + en bytes, los bytes se convierten primero en enteros y el resultado de la suma de dos enteros es un entero (32 bits).
fuente
int
produce esta promoción automática impulsada por el compilador : stackoverflow.com/a/43578929/4561887En términos de "por qué sucede en absoluto" es porque no hay operadores definidos por C # para aritmética con byte, sbyte, short o ushort, tal como lo han dicho otros. Esta respuesta es acerca de por qué esos operadores no están definidos.
Creo que es básicamente por el bien del rendimiento. Los procesadores tienen operaciones nativas para hacer aritmética con 32 bits muy rápidamente. Se podría hacer la conversión de vuelta del resultado a un byte automáticamente , pero daría lugar a penalizaciones de rendimiento en el caso en que realmente no desee ese comportamiento.
Creo que esto se menciona en uno de los estándares anotados de C #. Mirando...
EDITAR: molestamente, ahora he revisado la especificación ECMA C # 2 anotada, la especificación MS C # 3 anotada y la especificación CLI de anotación, y ninguno de ellos menciona esto hasta donde puedo ver. Estoy seguro de que he visto la razón dada anteriormente, pero me sorprende si sé dónde. Disculpas, fanáticos de referencia :(
fuente
Yo pensé que había visto esto antes en alguna parte. De este artículo, The Old New Thing :
EDITAR : Raymond está defendiendo, esencialmente, el enfoque que tomaron C y C ++ originalmente. En los comentarios, defiende el hecho de que C # adopta el mismo enfoque, sobre la base de la compatibilidad con versiones anteriores del lenguaje.
fuente
C#
ECMA-334 establece que la suma solo se define como legal en int + int, uint + uint, long + long y ulong + ulong (ECMA-334 14.7.4). Como tal, estas son las operaciones candidatas a considerar con respecto a 14.4.2. Debido a que hay conversiones implícitas de byte a int, uint, long y ulong, todos los miembros de la función de suma son miembros de la función aplicables según 14.4.2.1. Tenemos que encontrar el mejor elenco implícito de las reglas en 14.4.2.3:
La conversión (C1) a int (T1) es mejor que la conversión (C2) a uint (T2) o ulong (T2) porque:
La conversión (C1) a int (T1) es mejor que la conversión (C2) a larga (T2) porque hay una conversión implícita de int a larga:
Por lo tanto, se utiliza la función int + int, que devuelve un int.
Lo cual es una forma muy larga de decir que está enterrado muy profundamente en la especificación C #.
CLI
La CLI funciona solo en 6 tipos (int32, native int, int64, F, O y &). (ECMA-335 partición 3 sección 1.5)
Byte (int8) no es uno de esos tipos y se convierte automáticamente en int32 antes de la adición. (ECMA-335 partición 3 sección 1.6)
fuente
byte3 = byte1 And byte2
sin una conversión, pero sin ayuda arrojará una excepción de tiempo de ejecución siint1 = byte1 + byte2
arroja un valor superior a 255. No sé si algún idioma permitiríabyte3 = byte1+byte2
y arrojaría una excepción cuando exceda 255, pero no arrojará una excepción siint1 = byte1+byte2
rinde un valor en el rango 256-510.Las respuestas que indican cierta ineficiencia al agregar bytes y truncar el resultado a un byte son incorrectas. Los procesadores x86 tienen instrucciones diseñadas específicamente para la operación de enteros en cantidades de 8 bits.
De hecho, para los procesadores x86 / 64, realizar operaciones de 32 bits o 16 bits es menos eficiente que las operaciones de 64 u 8 bits debido al byte de prefijo de operando que debe decodificarse. En máquinas de 32 bits, realizar operaciones de 16 bits conlleva la misma penalización, pero todavía hay códigos de operación dedicados para operaciones de 8 bits.
Muchas arquitecturas RISC tienen instrucciones eficientes de palabras / bytes nativos similares. Aquellos que generalmente no tienen un valor de almacenar y convertir a valor firmado de una longitud de bits.
En otras palabras, esta decisión debe haberse basado en la percepción de para qué es el tipo de byte, no debido a las ineficiencias subyacentes del hardware.
fuente
byte
(ychar
), pero no para loshort
que semánticamente es claramente un número.Recuerdo que una vez leí algo de Jon Skeet (no puedo encontrarlo ahora, seguiré buscando) sobre cómo el byte no sobrecarga el operador +. De hecho, al agregar dos bytes como en su muestra, cada byte se está convirtiendo implícitamente en un int. El resultado de eso es obviamente un int. Ahora, POR QUÉ esto fue diseñado de esta manera, esperaré a que el propio Jon Skeet publique :)
EDITAR: ¡Lo encontré! Gran información sobre este mismo tema aquí .
fuente
Esto se debe a desbordamiento y acarreo.
Si agrega dos números de 8 bits, podrían desbordarse en el noveno bit.
Ejemplo:
No estoy seguro, pero supongo que
ints
,longs
y sedoubles
les da más espacio porque son bastante grandes. Además, son múltiplos de 4, que son más eficientes para las computadoras, debido a que el ancho del bus de datos interno es de 4 bytes o 32 bits (64 bits se está volviendo más frecuente ahora) de ancho. Byte y short son un poco más ineficientes, pero pueden ahorrar espacio.fuente
De la especificación del lenguaje C # 1.6.7.5 7.2.6.2 Promociones numéricas binarias convierte ambos operandos a int si no puede encajarlo en varias otras categorías. Supongo que no sobrecargaron el operador + para tomar el byte como parámetro, pero quieren que actúe de manera algo normal, por lo que solo usan el tipo de datos int.
Especificación de lenguaje C #
fuente
Mi sospecha es que C # en realidad está llamando a lo
operator+
definido enint
(que devuelve un aint
menos que esté en unchecked
bloque), y está emitiendo implícitamente ambosbytes
/shorts
aints
. Es por eso que el comportamiento parece inconsistente.fuente
Esta fue probablemente una decisión práctica por parte de los diseñadores de idiomas. Después de todo, un int es un Int32, un entero con signo de 32 bits. Siempre que realice una operación entera en un tipo más pequeño que int, la mayoría de las CPU de 32 bits la convertirá en un int con signo de 32 bits. Eso, combinado con la probabilidad de desbordar enteros pequeños, probablemente cerró el trato. Le ahorra la tarea de verificar continuamente el flujo excesivo o insuficiente, y cuando el resultado final de una expresión en bytes estaría dentro del rango, a pesar de que en alguna etapa intermedia estaría fuera del rango, obtendrá un resultado correcto resultado.
Otro pensamiento: el flujo excesivo / insuficiente en estos tipos tendría que ser simulado, ya que no ocurriría naturalmente en las CPU objetivo más probables. ¿Por qué molestarse?
fuente
Esta es en su mayor parte mi respuesta relacionada con este tema, presentada primero a una pregunta similar aquí .
Todas las operaciones con números integrales más pequeños que Int32 se redondean hasta 32 bits antes del cálculo por defecto. La razón por la cual el resultado es Int32 es simplemente dejarlo como está después del cálculo. Si marca los códigos de operación aritméticos de MSIL, el único tipo numérico integral con el que operan es Int32 e Int64. Es "por diseño".
Si desea que el resultado vuelva a estar en formato Int16, es irrelevante si realiza la conversión en código, o el compilador (hipotéticamente) emite la conversión "bajo el capó".
Por ejemplo, para hacer aritmética Int16:
Los dos números se expandirían a 32 bits, se agregarían y luego se truncarían nuevamente a 16 bits, que es como MS pretendía que fuera.
La ventaja de usar short (o byte) es principalmente el almacenamiento en casos en los que tiene grandes cantidades de datos (datos gráficos, transmisión, etc.)
fuente
La suma no está definida para bytes. Por lo tanto, se envían a int para la adición. Esto es cierto para la mayoría de las operaciones matemáticas y bytes. (tenga en cuenta que así es como solía estar en los idiomas más antiguos, supongo que hoy es cierto).
fuente
Creo que es una decisión de diseño sobre qué operación era más común ... Si byte + byte = byte quizás mucha más gente se molestará al tener que enviar a int cuando se requiere un int como resultado.
fuente
Desde el código de .NET Framework:
Simplifique con .NET 3.5 y superior:
ahora puedes hacer:
fuente
He probado el rendimiento entre byte e int.
Con valores int:
Con valores de bytes:
Aquí el resultado:
byte: 3.57s 157mo, 3.71s 171mo, 3.74s 168mo con CPU ~ = 30%
int: 4.05s 298mo, 3.92s 278mo, 4.28 294mo con CPU ~ = 27%
Conclusión: el
byte usa más la CPU pero cuesta menos memoria y es más rápido (tal vez porque hay menos bytes para asignar)
fuente
Además de todos los otros excelentes comentarios, pensé que agregaría un pequeño dato. Muchos comentarios se han preguntado por qué int, long y prácticamente cualquier otro tipo numérico no sigue esta regla ... devuelve un tipo "más grande" en respuesta a la aritmética.
Muchas respuestas han tenido que ver con el rendimiento (bueno, 32 bits es más rápido que 8 bits). En realidad, un número de 8 bits sigue siendo un número de 32 bits para una CPU de 32 bits ... incluso si agrega dos bytes, la porción de datos en la que opera la CPU será de 32 bits independientemente ... por lo que agregar ints no va a ser "más rápido" que agregar dos bytes ... todo es lo mismo a la CPU. AHORA, agregar dos entradas SERÁ más rápido que agregar dos largos en un procesador de 32 bits, porque agregar dos largos requiere más microops ya que está trabajando con números más anchos que la palabra del procesador.
Creo que la razón fundamental para hacer que la aritmética de bytes produzca ints es bastante clara y directa: ¡8 bits simplemente no llega muy lejos! : D Con 8 bits, tiene un rango sin signo de 0-255. Eso no es mucho espacio para trabajar ... la probabilidad de que encuentres limitaciones de bytes es MUY alta cuando las usas en aritmética. Sin embargo, la posibilidad de que se quede sin bits cuando trabaje con ints, o longs, o dobles, etc. es significativamente menor ... lo suficientemente baja como para que raramente encontremos la necesidad de más.
La conversión automática de byte a int es lógica porque la escala de un byte es muy pequeña. La conversión automática de int a long, float a double, etc. no es lógica porque esos números tienen una escala significativa.
fuente
byte - byte
regresaint
, o por qué no lanzan ashort
...byte + byte
devuelveint
, debido a que 255 + cualquier cosa es mayor de lo que puede contener un byte, no tiene sentido que ningún byte menos cualquier otro byte devuelva otra cosa que no sea un int desde un punto de vista de consistencia de tipo de retorno.byte
resta devolvería abyte
, y la suma de bytes devolvería ashort
(byte
+byte
siempre encajará en ashort
). Si se tratara de consistencia como usted dice, entoncesshort
sería suficiente para ambas operaciones en lugar de hacerloint
. Claramente, hay una mezcla de razones, no todas ellas necesariamente bien pensadas. O bien, la razón de rendimiento que figura a continuación puede ser más precisa.