C# has the following predefined numeric types:
C# type | System type | Suffix | Size | Range |
---|---|---|---|---|
Integral—signed | ||||
|
| 8 bits | –27 to 27–1 | |
|
| 16 bits | –215 to 215–1 | |
|
| 32 bits | –231 to 231–1 | |
|
|
| 64 bits | –263 to 263–1 |
Integral—unsigned | ||||
|
| 8 bits | 0 to 28–1 | |
|
| 16 bits | 0 to 216–1 | |
|
|
| 32 bits | 0 to 232–1 |
|
|
| 64 bits | 0 to 264–1 |
Real | ||||
|
|
| 32 bits | ± (~10–45 to 1038) |
|
|
| 64 bits | ± (~10–324 to 10308) |
|
|
| 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.)
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
.
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)
The numeric suffixes listed in the preceding table explicitly define the type of a literal:
decimal d = 3.5M
; // M = decim
al (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
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
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.
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
The arithmetic operators (+
, -
,
*
, /
, %
) are
defined for all numeric types
except the 8- and 16-bit integral types. The %
operator evaluates the remainder after
division.
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
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.
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
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 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 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
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);
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 |
|
|
---|---|---|
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 |
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).
3.145.101.81