Numeric Types

C# has the following predefined numeric types:

C# type

System type

Suffix

Size

Range

Integral—signed

 

sbyte

SByte

 

8 bits

–27 to 27–1

short

Int16

 

16 bits

–215 to 215–1

int

Int32

 

32 bits

–231 to 231–1

long

Int64

L

64 bits

–263 to 263–1

Integral—unsigned

 

byte

Byte

 

8 bits

0 to 28–1

ushort

UInt16

 

16 bits

0 to 216–1

uint

UInt32

U

32 bits

0 to 232–1

ulong

UInt64

UL

64 bits

0 to 264–1

Real

 

float

Single

F

32 bits

± (~10–45 to 1038)

double

Double

D

64 bits

± (~10–324 to 10308)

decimal

Decimal

M

128 bits

± (~10–28 to 1028)

Of the integral types, int and long are first-class citizens and are favored by both C# and the runtime. The other integral types are typically used for interoperability or when space efficiency is paramount.

Of the real number types, float and double are called floating-point types and are typically used for scientific calculations. The decimal type is typically used for financial calculations, where base-10-accurate arithmetic and high precision are required. (Technically, decimal is a floating-point type too, although it’s not generally referred to as such.)

Numeric Literals

Integral literals can use decimal or hexadecimal notation; hexadecimal is denoted with the 0x prefix (e.g., 0x7f is equivalent to 127). Real literals may use decimal or exponential notation such as 1E06.

Numeric literal type inference

By default, the compiler infers a numeric literal to be either double or an integral type:

  • If the literal contains a decimal point or the exponential symbol (E), it is a double.

  • Otherwise, the literal’s type is the first type in this list that can fit the literal’s value: int, uint, long, and ulong.

For example:

Console.Write (       1.0.GetType());  // Double (double)
Console.Write (      1E06.GetType());  // Double (double)
Console.Write (         1.GetType());  // Int32  (int)
Console.Write (0xF0000000.GetType());  // UInt32 (uint)

Numeric suffixes

The numeric suffixes listed in the preceding table explicitly define the type of a literal:

decimal d = 3.5M;   // M = decimal (case-insensitive)

The suffixes U and L are rarely necessary, because the uint, long, and ulong types can nearly always be either inferred or implicitly converted from int:

long i = 5;     // Implicit conversion from int to long

The D suffix is technically redundant, in that all literals with a decimal point are inferred to be double (and you can always add a decimal point to a numeric literal). The F and M suffixes are the most useful and are mandatory when specifying fractional float or decimal literals. Without suffixes, the following would not compile, because 4.5 would be inferred to be of type double, which has no implicit conversion to float or decimal:

float f = 4.5F;       // Won't compile without suffix
decimal d = -1.23M;   // Won't compile without suffix

Numeric Conversions

Integral to integral conversions

Integral conversions are implicit when the destination type can represent every possible value of the source type. Otherwise, an explicit conversion is required. For example:

int x = 12345;       // int is a 32-bit integral
long y = x;          // Implicit conversion to 64-bit int
short z = (short)x;  // Explicit conversion to 16-bit int

Real to real conversions

A float can be implicitly converted to a double, because a double can represent every possible float value. The reverse conversion must be explicit.

Conversions between decimal and other real types must be explicit.

Real to integral conversions

Conversions from integral types to real types are implicit, whereas the reverse must be explicit. Converting from a floating-point to an integral truncates any fractional portion; to perform rounding conversions, use the static System.Convert class.

A caveat is that implicitly converting a large integral type to a floating-point type preserves magnitude but may occasionally lose precision:

int i1 = 100000001;
float f = i1;      // Magnitude preserved, precision lost
int i2 = (int)f;   // 100000000

Arithmetic Operators

The arithmetic operators (+, -, *, /, %) are defined for all numeric types except the 8- and 16-bit integral types. The % operator evaluates the remainder after division.

Increment and Decrement Operators

The increment and decrement operators (++, --) increment and decrement numeric types by 1. The operator can either precede or follow the variable, depending on whether you want the variable to be updated before or after the expression is evaluated. For example:

int x = 0;
Console.WriteLine (x++);   // Outputs 0; x is now 1
Console.WriteLine (++x);   // Outputs 2; x is now 2
Console.WriteLine (--x);   // Outputs 1; x is now 1

Specialized Integral Operations

Integral division

Division operations on integral types always truncate remainders (round toward zero). Dividing by a variable whose value is zero generates a runtime error (a DivideByZeroException). Dividing by the literal or constant 0 generates a compile-time error.

Integral overflow

At runtime, arithmetic operations on integral types can overflow. By default, this happens silently—no exception is thrown and the result exhibits wraparound behavior, as though the computation was done on a larger integer type and the extra significant bits discarded. For example, decrementing the minimum possible int value results in the maximum possible int value:

int a = int.MinValue; a--;
Console.WriteLine (a == int.MaxValue); // True

The checked and unchecked operators

The checked operator tells the runtime to generate an OverflowException rather than overflowing silently when an integral expression or statement exceeds the arithmetic limits of that type. The checked operator affects expressions with the ++, ––, (unary) , +, , *, /, and explicit conversion operators between integral types.

You can use checked around either an expression or a statement block. For example:

int a = 1000000, b = 1000000;

int c = checked (a * b);   // Checks just the expression

checked                    // Checks all expressions
{                          // in statement block.
   c = a * b;
   ...
}

You can make arithmetic overflow checking the default for all expressions in a program by compiling with the /checked+ command-line switch (in Visual Studio, go to Advanced Build Settings). If you then need to disable overflow checking just for specific expressions or statements, you can do so with the unchecked operator.

Bitwise operators

C# supports the following bitwise operators:

Operator

Meaning

Sample expression

Result

~

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

0x10

8- and 16-Bit Integrals

The 8- and 16-bit integral types are byte, sbyte, short, and ushort. These types lack their own arithmetic operators, so C# implicitly converts them to larger types as required. This can cause a compilation error when trying to assign the result back to a small integral type:

short x = 1, y = 1;
short z = x + y;          // Compile-time error

In this case, x and y are implicitly converted to int so that the addition can be performed. This means the result is also an int, which cannot be implicitly cast back to a short (because it could cause loss of data). To make this compile, we must add an explicit cast:

short z = (short) (x + y);   // OK

Special Float and Double Values

Unlike integral types, floating-point types have values that certain operations treat specially. These special values are NaN (Not a Number), +∞, –∞, and –0. The float and double classes have constants for NaN, +∞, and –∞ (as well as other values including MaxValue, MinValue, and Epsilon). For example:

Console.Write (double.NegativeInfinity);   // -Infinity

Dividing a nonzero number by zero results in an infinite value:

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

Dividing zero by zero, or subtracting infinity from infinity, results in a NaN:

Console.Write ( 0.0 / 0.0);                 //  NaN
Console.Write ((1.0 / 0.0) – (1.0 / 0.0));  //  NaN

When using ==, a NaN value is never equal to another value, even another NaN value. To test whether a value is NaN, you must use the float.IsNaN or double.IsNaN method:

Console.WriteLine (0.0 / 0.0 == double.NaN);    // False
Console.WriteLine (double.IsNaN (0.0 / 0.0));   // True

When using object.Equals, however, two NaN values are equal:

bool isTrue = object.Equals (0.0/0.0, double.NaN);

double Versus decimal

For scientific computations (such as computing spatial coordinates), double is useful. decimal is useful for financial computations and values that are “man-made” rather than the result of real-world measurements. Here’s a summary of the differences:

Feature

double

decimal

Internal representation

Base 2

Base 10

Precision

15-16 significant figures

28-29 significant figures

Range

±(~10–324 to ~10308)

±(~10–28 to ~1028)

Special values

+0, –0, +∞, –∞, and NaN

None

Speed

Native to processor

Non-native to processor (about 10 times slower than double)

Real Number Rounding Errors

float and double internally represent numbers in base 2. For this reason, most literals with a fractional component (which are in base 10) will not be represented precisely:

float tenth = 0.1f;                     // Not quite 0.1
float one   = 1f;
Console.WriteLine (one - tenth * 10f);  // -1.490116E-08

This is why float and double are bad for financial calculations. In contrast, decimal works in base 10 and so can precisely represent fractional numbers such as 0.1 (whose base 10 representation is nonrecurring).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.176.99