sirve - tamaño de int c#
Adición de int y uint (6)
¿Por qué int + int = int y uint + uint = uint, pero int + uint = long? ¿Cuál es la motivación para esta decisión?
La forma en que se formula la pregunta implica la presuposición de que el equipo de diseño quería que int + uint sea larga, y eligió reglas de tipo para alcanzar ese objetivo. Esa presuposición es falsa.
Más bien, el equipo de diseño pensó:
- ¿Qué operaciones matemáticas es más probable que realicen las personas?
- ¿Qué operaciones matemáticas se pueden realizar de manera segura y eficiente?
- ¿Qué conversiones entre tipos numéricos se pueden realizar sin pérdida de magnitud y precisión?
- ¿Cómo pueden las reglas para la resolución de operadores ser simples y consistentes con las reglas para la resolución de sobrecarga de métodos?
Además de muchas otras consideraciones, como si el diseño funciona a favor o en contra de los programas de depuración, mantenibles y de versión, entre otros. (Observo que no estuve en la sala para esta reunión de diseño en particular, ya que es anterior al tiempo que tengo en el equipo de diseño. Pero he leído sus notas y conozco el tipo de cosas que hubieran preocupado al equipo de diseño durante este período).
La investigación de estas preguntas llevó al diseño actual: las operaciones aritméticas se definen como int + int -> int, uint + uint -> uint, long + long -> long, int se puede convertir a largo, uint se puede convertir a largo, y así sucesivamente.
Una consecuencia de estas decisiones es que al agregar uint + int, la resolución de sobrecarga elige long + long como la coincidencia más cercana, y long + long es larga, por lo tanto, uint + int es larga.
Hacer que uint + int tenga un comportamiento más diferente que podría considerar más sensato no fue un objetivo de diseño del equipo en absoluto porque mezclar valores firmados y sin firmar es primero, raro en la práctica, y segundo, casi siempre es un error. El equipo de diseño podría haber agregado casos especiales para cada combinación de enteros de uno, dos, cuatro y ocho bytes con y sin signo, así como char, float, double y decimal, o cualquier subconjunto de esos cientos de casos, pero eso funciona Contra el objetivo de la simplicidad.
En resumen, por un lado, tenemos una gran cantidad de trabajo de diseño para hacer que una característica que no queremos que nadie use sea más fácil de usar a costa de una especificación enormemente complicada. Por otro lado, tenemos una especificación simple que produce un comportamiento inusual en un caso raro que esperamos que nadie encuentre en la práctica. Dadas esas opciones, ¿cuál elegirías? El equipo de diseño de C # eligió este último.
Me sorprende el comportamiento del compilador de C # en el siguiente ejemplo:
int i = 1024;
uint x = 2048;
x = x+i; // A error CS0266: Cannot implicitly convert type ''long'' to ''uint'' ...
Parece bien ya que int + uint
puede desbordarse. Sin embargo, si uint
se cambia a int
, el error desaparece, ya que int + int
no puede int + int
un desbordamiento:
int i = 1024;
int x = 2048;
x = x+i; // OK, int
Por otra parte, uint + uint = uint
:
uint i = 1024;
uint x = 2048;
x = x+i; // OK, uint
Parece totalmente oscuro.
¿Por qué int + int = int
y uint + uint = uint
, pero int + uint = long
?
¿Cuál es la motivación para esta decisión?
Creo que el comportamiento del compilador es bastante lógico y esperado.
En el siguiente código:
int i;
int j;
var k = i + j;
Hay una sobrecarga exacta para esta operación, entonces k
es int
. La misma lógica se aplica al agregar dos uint
, dos byte
o lo que tengas. El trabajo del compilador es fácil aquí, es feliz porque la resolución de sobrecarga encuentra una coincidencia exacta. Existe una gran posibilidad de que la persona que escribe este código espere que k
sea un int
y es consciente de que la operación puede desbordarse en ciertas circunstancias.
Ahora considere el caso que está preguntando acerca de:
uint i;
int j;
var k = i + j;
¿Qué ve el compilador? Bueno, ve una operación que no tiene una sobrecarga coincidente; no hay operador +
sobrecarga que tome un int
y una uint
como sus dos operandos. Por lo tanto, el algoritmo de resolución de sobrecarga continúa y trata de encontrar una sobrecarga de operador que pueda ser válida. Esto significa que tiene que encontrar una sobrecarga donde los tipos involucrados pueden "mantener" los operandos originales; es decir, tanto i
como j
tienen que ser convertibles implícitamente a dicho tipo (s).
El compilador no puede convertir implícitamente uint
a int
porque dicha conversión no existe. No se puede convertir implícitamente int
a uint
porque la conversión tampoco existe (ambos pueden causar un cambio en la magnitud). Así que la única opción que tiene realmente es elegir el primer tipo más amplio que pueda "mantener" ambos tipos de operandos, que en este caso es long
. Una vez que ambos operandos se convierten implícitamente a k
long
es obvio.
La motivación de este comportamiento es, IMO, elegir la opción disponible más segura y no adivinar la intención dudosa del codificador. El compilador no puede hacer una suposición educada sobre lo que la persona que escribe este código espera que sea k
. Un int
? Bueno, ¿por qué no un uint
? Ambas opciones parecen igualmente malas. El compilador elige la única ruta lógica; La segura: long
. Si el codificador quiere que k
sea int
o unit
, solo tiene que lanzar explícitamente uno de los operandos.
Y por último, pero no menos importante, el algoritmo de resolución de sobrecarga del compilador de C # no considera el tipo de retorno al decidir la mejor sobrecarga. Por lo tanto, el hecho de que esté almacenando el resultado de la operación en un uint
es completamente irrelevante para el compilador y no tiene ningún efecto en el proceso de resolución de sobrecarga.
Esto es toda especulación de mi parte, y puedo estar completamente equivocado. Pero sí parece razonamiento lógico.
Esta es una manifestación de resolución de sobrecarga para tipos numéricos.
La promoción numérica consiste en realizar automáticamente ciertas conversiones implícitas de los operandos de los operadores numéricos unarios y binarios predefinidos. La promoción numérica no es un mecanismo distinto, sino un efecto de aplicar la resolución de sobrecarga a los operadores predefinidos. La promoción numérica específicamente no afecta la evaluación de los operadores definidos por el usuario, aunque los operadores definidos por el usuario pueden implementarse para mostrar efectos similares.
http://msdn.microsoft.com/en-us/library/aa691328(v=vs.71).aspx
Si echas un vistazo a
long operator *(long x, long y);
uint operator *(uint x, uint y);
desde ese enlace, verá que hay dos posibles sobrecargas (el ejemplo se refiere al operator *
, pero lo mismo es cierto para el operator +
).
La uint
se convierte implícitamente a un long
para resolución de sobrecarga, como es int
.
Desde uint hasta long, ulong, float, double o decimal.
Desde int hasta long, float, double o decimal.
http://msdn.microsoft.com/en-us/library/aa691282(v=vs.71).aspx
¿Cuál es la motivación para esta decisión?
Probablemente se necesitaría un miembro del equipo de diseño para responder a ese aspecto. Eric Lippert, ¿dónde estás? :-) Tenga en cuenta que el razonamiento de @ Nicolas a continuación es muy plausible, que ambos operandos se convierten al tipo "más pequeño" que puede contener el rango completo de valores para cada operando.
La respuesta corta es "porque la Norma dice que será así", que se encuentra en el §14.2.5.2 informativo de ISO 23270. La normativa 13.1.2. (Conversiones numéricas implícitas) dice:
Las conversiones numéricas implícitas son:
...
- Desde
int
hastalong
,float
,double
odecimal
.- Desde
uint
hastalong
,ulong
,float
,double
odecimal
....
Las conversiones de
int
,uint
,long
oulong
afloat
y delong
oulong
adouble
pueden causar una pérdida de precisión, pero nunca causarán una pérdida de magnitud. Las otras conversiones numéricas implícitas nunca pierden ninguna información. ( enf. mio )
La respuesta [ligeramente] más larga es que estás agregando dos tipos diferentes: un entero con signo de 32 bits y un entero sin signo de 32 bits:
- el dominio de un entero de 32 bits con signo es -2,147,483,648 (0x80000000) - +2,147,483,647 (0x7FFFFFFF).
- el dominio de un entero de 32 bits sin signo es 0 (0x00000000) - +4,294,967,295 (0xFFFFFFFF).
Por lo tanto, los tipos no son compatibles, ya que un int
no puede contener un uint
arbitrario y un uint
no puede contener un int
arbitrario. Se convierten implícitamente (una conversión de ampliación , según el requisito de §13.1.2 de que no se pierda información) al siguiente tipo más grande que puede contener ambos: un long
en este caso, un entero de 64 bits con signo, que tiene el dominio -9,223,372,036,854,775,808 (0x800000000000000000) - +9,223,372,036,854,775,807 (0x7FFFFFFFFFFFFFFF).
Editado para tener en cuenta: como un aparte, ejecutando este código:
var x = 1024 + 2048u ;
Console.WriteLine( "''x'' is an instance of `{0}`" , x.GetType().FullName ) ;
No rinde long
ejemplo del cartel original. En cambio, lo que se produce es:
''x'' is an instance of `System.UInt32`
Esto es debido al plegamiento constante . El primer elemento en la expresión, 1024
no tiene sufijo y, como tal, es un int
y el segundo elemento en la expresión 2048u
es una uint
, de acuerdo con las reglas:
- Si el literal no tiene sufijo, tiene el primero de estos tipos en los que se puede representar su valor:
int
,uint
,long
,ulong
.- Si el literal está sufijado por
U
ou
, tiene el primero de estos tipos en los que se puede representar su valor:uint
,ulong
.
Y como el optimizador sabe cuáles son los valores, la suma se calcula previamente y se evalúa como una uint
.
La consistencia es el duende de las mentes pequeñas.
Las reglas de promoción numérica para C # se basan libremente en las de Java y C, que funcionan al identificar un tipo al que se pueden convertir ambos operandos y luego hacer que el resultado sea del mismo tipo. Creo que tal enfoque fue razonable en la década de 1980, pero los idiomas más nuevos deberían dejarlo de lado en favor de uno que analice cómo se utilizan los valores (por ejemplo, si estaba diseñando un idioma, entonces se asignaron Int32 i1,i2,i3; Int64 l;
un compilador procesaría i4=i1+i2+i3;
usando matemática de 32 bits [lanzar una excepción en caso de desbordamiento] procesaría l=i1+i2+i3;
con matemática de 64 bits.) pero las reglas de C # son lo que son y no parecen ser propensos a cambiar.
Debe tenerse en cuenta que, por definición, las reglas de promoción de C # siempre seleccionan las sobrecargas que se consideran "más adecuadas" según la especificación del idioma, pero eso no significa que sean realmente las más adecuadas para cualquier propósito útil. Por ejemplo, double f=1111111100/11111111.0f;
Parecería que debería rendir 100.0, y se calcularía correctamente si ambos operandos se promovieran al double
, pero el compilador, en cambio, convertiría el número entero 1111111100 en float
con 1111111040.0f, y luego realizaría la división con 99.999992370605469
i int i = 1024;
uint x = 2048;
// Technique #1
x = x + Convert.ToUInt32(i);
// Technique #2
x = x + checked((uint)i);
// Technique #3
x = x + unchecked((uint) i);
// Technique #4
x = x + (uint)i;