2.4.5 Operaciones Especiales sobre Tipos Integrales en C#

Los tipos integrales son int, uint, long, ulong, short, ushort, bute, y sbyte.
División
Las operaciones de divisón en tipos integrales siempre eliminan el residuo (redondeando hacia 0). Dividir entre una variable cuyo valor sea 0 genera un error en tiempo de ejecución (una excepción DivideByZeroException):
int a = 2 / 3; // 0
int b = 0;
int c = 5 / b; // arroja DivideByZeroException
Dividir entre una literal o constante 0 genera un error en tiempo de compilación.
Desbordamiento
En tiempo de ejecución, las operaciones aritméticas sobre tipos integrales se pueden derbordar. Por defecto, esto sucede de forma silenciosa - no se arroja una excepción como si el cálculo se hubiera hecho sobre un tipo entero más grande y los bits extra se descartaran. Por ejemplo, el decremento hasta el mínimo posible de un valor int resulta en el valor máximo posible de int:
int a = int.MinValue;
a--;
Console.WriteLine (a == int.MaxValue); // True
Operadores de verificación de desbordamiento
El operador checked envía la instrucción al runtime para que genere una OverFlowException en lugar de desbordarse silenciosamente cuando una expresión de tipo integral o una sentencia exceden los límites aritméticos de ese tipo. El operador checked afecta expresiones con los operadores ++, --, +, -, (binario y unario), *, /, y los operadores deconversión explícita entre tipos integrales. La verificación de desboramiendo implica pequeños costos de desempeño.
El operador checked no tiene efecto sobre los tipos double y float (que se desbordan hacia valores especiales "infinitos") ni sobre el tipo decimal (que siempre es verificado).
Podemos usar checked tanto en una expresión como en un bloque de sentecias./
int a = 1000000;
int b = 1000000;
int c = checked (a * b); // Sólo verifica la expresión.
checked // Verifica todas las sentencias
{ // dentro del bloque.
...
c = a * b;
...
}
Podemos activar la verificación de desbordamiento aritmético para todas las expresiones de un programa mediante la selección de la opción "checked" al nivel del proyecto (en Visual Studio, ir a Build Advanced Settings). Si necesitamos desactivar la verificación para algunas instruccioines o sentencias específicas, podemos hacerlo usando el operador unchecked. Por ejemplo, el siguiente código no arrojará excepciones - aún cuando la opción "checked" del proyecto esté activada:
int x = int.MaxValue;
int y = unchecked (x + 1);
unchecked { int z = x + 1; }
Verificación de desbordamiento para expresiones constantes
Sin importar si la opción "checked" esté activada para el proyecto, las expresiones evaluadas en tiempo de compilación siempre serán verificadas - a menos que se aplique el operador unchecked
int x = int.MaxValue + 1; // Error en tiempo de compilación
int y = unchecked (int.MaxValue + 1); // Sin errores
Operadores a nivel de bits
C# soporta los siguientes operadores a nivel de bits:
| Operador | Significado | Ejemplo | Resultado | ||
~ | Complement | ~0xfU | 0xfffffff0U | ||
& | And | 0xf0 & 0x33 | 0x30 | ||
| ` | ` | Or | `0xf0 | 0x33` | 0xf3 |
^ | Exclusive Or | 0xff00 ^ 0x0ff0 | 0xf0f0 | ||
<< | Shift left | 0x20 << 2 | 0x80 | ||
>> | Shift right | 0x20 >> 1 | 0x80 |
Apartir de .NET 6, se exponen operaciones a nivel de bits adicionales mediante una nueva clase llamada BitOperations en el namespace System.Numerics.
Tipos integrales de 8 y 16 bits
Los tipos integrales de 8 y 16 bits son byte, sbyte, short, y ushort. Estos tipos carecen de operadores aritméticos propios, por lo que C# los convierte de forma implícita a tipos tan grandes como se necesite. Esto puede causar errores en timepo de compilación al tratar de asignar el resultado a un tipo integral más pequeño:
short x = 1, y = 1;
short z = x + y; // Error en tiempo de compilación
En este caso, x y y fueron convertidos implícitamente a int para que la suma pueda ser efectuada. Esto significa que el resultado es también un int que no puede ser almacenado de vuelta en un short (causaría pérdida de datos). Para lograr que esto compile, se debe agregar una conversión explícita:
short z = (short) (x + y); // OK
Valores Float y Double especiales
Al contrario de los tipos integrales, los tipos de punto flotante tienen valores que en ciertas operacions tienen un tratamiento especial. Estos valores son NaN (Not a Number), +∞, −∞, y −0. Las clases double y float tienen constantes para estos valores igual que para MaxValue, MinValue, y Epsilon; por ejemplo:
Console.WriteLine (double.NegativeInfinity); // -Infinity
Las constantes que representan valores especiales para double y float son las siguientes:
| Valore especial | Constante double | Constante float |
| NaN | double.NaN | float.NaN |
| +∞ | double.PositiveInfinity | float.PositiveInfinity |
| −∞ | double.NegativeInfinity | float.NegativeInfinity |
| -0 | −0.0 | −0.0f |
Dividir un valor diferente a 0 entre cero resulta en un valor infinito:
Console.WriteLine ( 1.0 / 0.0); // Infinity
Console.WriteLine (−1.0 / 0.0); // -Infinity
Console.WriteLine ( 1.0 / −0.0); // -Infinity
Console.WriteLine (−1.0 / −0.0); // Infinity
// Salida
// ∞
// -∞
// -∞
// ∞
Dividir cero entre cero, o restar infinito a infinito, resulta en NaN:
Console.WriteLine ( 0.0 / 0.0); // NaN
Console.WriteLine ((1.0 / 0.0) - (1.0 / 0.0)); // NaN
Cuando se usa el operador de comparación, un valor NaN nunca es igual a otro valor, incluso comparándolo con otro NaN:
Console.WriteLine (0.0 / 0.0 == double.NaN); // False
Para compronar si un valor es NaN, debemos usar los métodos float.IsNaN o double.IsNaN:
Console.WriteLine (double.IsNaN (0.0 / 0.0)); // True
Sin embargo, si usamos object.Equals, dos NaN serán iguales:
Console.WriteLine (object.Equals (0.0 / 0.0, double.NaN)); // True
float y double siguen las especificaciones de la IEEE 754 para formatos de tipos, soportados de forma nativa casi por todos los procesadores.
double vs decimal
double es útil para cálculos científicos (como el procesamiento de coordenadas espaciales). decimal es útil para cálculos financieros y valores "hechos por el hombre" que no resultan de medidas del mundo real. Aquí un resumen de las diferencias:

Errores de redondeo en números reales
float y double representan internamente números en base 2. Por esta razón, solo los números expresables en base 2 se representan con precisión. Esto significa que la mayoría de las literales con un componente fraccional (que está en base 10) no será representado de forma precisa; por ejemplo:
float x = 0.1f; // No exactamente 0.1
Console.WriteLine (x + x + x + x + x + x + x + x + x + x); // 1.0000001
Por esta razón double y float son malos en los cálculos financieros. En contraste, decimal funciona en base 10 y de esta forma puede representar números en base 10 (y sus factores 2 y 5) de forma precisa. Dado que los números reales son en base 10, decimal puede representar con precisión números como 0.1. Sin embargo, ni double ni decimal pueden representar con precisión un número fraccional cuya representación en base 10 es recurrente:
decimal m = 1M / 6M; // 0.1666666666666666666666666667M
double d = 1.0 / 6.0; // 0.16666666666666666
Esto sólo generaría acumulación de errores por redondeo:
decimal notQuiteWholeM = m+m+m+m+m+m; // 1.0000000000000000000000000002M
double notQuiteWholeD = d+d+d+d+d+d; // 0.99999999999999989
Lo que invalida la igualdad en operaciones de comparación:
Console.WriteLine (notQuiteWholeM == 1M); // False
Console.WriteLine (notQuiteWholeD < 1.0); // True



