C# has the following predefined numeric types.
C# type |
System type |
Suffix |
Size |
Range |
---|---|---|---|---|
Integral—signed | ||||
|
|
|
| |
|
|
|
| |
|
|
|
| |
|
|
|
|
|
Integral—unsigned | ||||
|
|
|
| |
|
|
|
| |
|
|
|
|
|
|
|
|
|
|
Real | ||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Of the integral types, int
and
long
are first-class citizens and C# and the runtime
favor both. 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.[1]
Integral literals can use decimal or hexadecimal notation;
hexadecimal is denoted with the 0x
; prefix. For
example:
int x = 127; long y = 0x7F;
Real literals can use decimal and/or exponential notation. For example:
double d = 1.5; double million = 1E06;
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, ulong
, and long
.
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 explicitly define the type of a literal (suffixes can be either lower- or uppercase):
Category |
C# type |
Notes |
Example |
---|---|---|---|
|
|
| |
|
|
| |
|
|
| |
|
|
Combinable with
|
|
|
|
Combinable with
|
|
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 lossless conversion from // int literal 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:
double x = 4.0;
The F
and M
suffixes are the most useful and should always be applied when specifying float
or decimal
literals. Without the F
suffix, the following line
would not compile because 4.5 would be inferred to be of type double
, which has no implicit conversion to float
:
float f = 4.5F;
The same principle is true for a decimal literal:
decimal x = -1.23M; // Will not compile without // the M suffix.
The semantics of numeric conversions are described in detail in the following section.
Integral conversions are implicit when the destination type can represent every possible value of the source type. Other-wise, an explicit conversion is required.
A float
can be implicitly converted to a double
,as a double
can
represent every possible value of a float
. The
reverse conversion must be explicit.
All integral types may be implicitly converted to all floating point numbers:
int i = 1; float f = i;
The reverse conversion must be explicit:
int i2 = (int)f;
When you cast from a floating-point number to an integral, any fractional portion
is truncated; no rounding is performed. The static class System.Convert
provides methods that round while converting between
various numeric types.
Implicitly converting a large integral type to a floating-point type preserves magnitude but may occasionally lose precision. This is because floating-point types always have more magnitude than integral types, but they may have less precision. Rewriting our example with a larger number demonstrates:
int i1 = 100000001; float f = i1; // Magnitude preserved, precision lost int i2 = (int)f; // 100000000
The arithmetic operators (+, -, *, /, %) are defined for all numeric types except the 8- and 16-bit integral types:
+ Addition - Subtraction * Multiplication / Division % Remainder after division
The increment and decrement operators (++, --) increment and decrement numeric types by one. 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
Division operations on integral types always truncate remainders. Dividing by a
variable whose value is 0 generates a runtime error (a DivisionByZeroException
). Dividing by the literal 0 generates a
compile-time error.
At runtime, arithmetic operations on
integral types can overflow. By default, this happens silently—no exception is thrown.
While the C# specification is agnostic as to the result of an overflow, the CLR always
causes wraparound behavior. 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
operator tells the runtime to
generate an OverflowException
rather than failing
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.
checked
can be used around either an expression
or a statement block. For example:
int a = 1000000, b = 1000000; int c =checked
(a*b); // Checks just the expressionchecked
// 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.
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 compile-time 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
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 (MaxValue, MinValue
, and Epsilon
). For example:
Console.Write (double.Negative Infinity); // -Infinity
The constants that represent special values for double
and float
are as follows.
Special value |
Double constant |
Float constant |
---|---|---|
NaN |
|
|
+∞ |
|
|
-∞ |
|
|
–0 |
|
|
Dividing a nonzero number by zero results in an infinite value. For example:
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. For example:
Console.WriteLine ( 0.0 / 0.0); // NaN Console.WriteLine ((1.0 / 0.0) - (1.0 / 0.0)); // NaN
When using = =, a NaN value is never equal to another value, even another NaN value:
Console.WriteLine (0.0 / 0.0 == double.NaN); // False
To test whether a value is NaN, you must use the float.IsNaN
or double.IsNaN
method, as
follows:
Console.WriteLine (double.IsNaN (0.0 / 0.0)); // True
float
and double
follow the specification of the IEEE 754 format types, supported
natively by almost all processors. You can find detailed information on the behavior of
these types at http://www.ieee.org.
When using object.Equals
, however, two NaN values are equal:
Console.WriteLine (object.Equals (0.0 / 0.0, double.NaN)); // True
double
is useful for scientific computations (such
as computing spatial coordinates); decimal
is useful
for financial computations.
Category |
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 |
None |
Speed |
Native to processor |
Nonnative to processor (about 10 times slower than |
float
and double
internally represent numbers in base 2. For this reason, only numbers expressible in base
2 are represented precisely. Practically, this means most literals with a fractional
component (which are in base 10) will not be represented precisely. For example:
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
numbers expressible in base 10 (as well as its factors, base 2 and base 5). Because real
literals are in base 10, decimal
can precisely
represent numbers such as 0.1. However, neither double
nor decimal
can precisely represent a fractional number
whose base–10 representation is recurring:
decimal m = 1M / 6M; // 0.1666666666666666666666666667M double d = 1.0 / 6.0; // 0.16666666666666666
This leads to accumulated rounding errors:
decimal notQuiteWholeM = m+m+m+m+m+m; // 1.0000000000000000000000000002M double notQuiteWholeD = d+d+d+d+d+d; // 0.99999999999999989
which breaks equality and comparison operations:
Console.WriteLine (notQuiteWholeM == 1M); // False Console.WriteLine (notQuiteWholeD < 1.0); // True
[1] Technically, decimal is a floating-point type too, although it’s not referred to as such in the C# language specification.