表2-1中列出了C#中所有的预定义数值类型。
表2-1:C#中的预定义数值类型
在整数类型中, int 和 long 是最基本的类型,C#和运行时对它们都有良好的支持。其他的整数类型通常用于实现互操作性或优化存储空间使用效率等情况。 nint 和 unint 是C# 9引入的原生大小的整数类型,它们适用于执行指针算法,我们将在4.18.8节进行介绍。
在实数类型中, float 和 double 称为浮点类型 [2] ,并通常用于科学和图形计算。 decimal 类型通常用于金融计算这种十进制下的高精度算术运算。
从.NET 5开始,运行时引入了一种16位浮点类型,称为 Half 。该类型主要针对与显卡处理器的互操作,大多数CPU都没有对这种类型提供原生的支持。 Half 并非CLR的基元类型,C#对该类型也不存在特殊的语言支持。
整数类型字面量可以使用十进制或者十六进制表示,十六进制辅以 0x 前缀,例如:
我们可以在数值字面量的任意位置加入下划线以方便阅读:
也可以用 0b 前缀使用二进制表示数值:
实数字面量可以用小数或指数表示,例如:
默认情况下,编译器将数值字面量推断为 double 类型或者整数类型:
· 如果这个字面量包含小数点或者指数符号(E),那么它是 double 。
· 否则,这个字面量的类型就是下列能满足这个字面量的第一个类型: int 、 uint 、 long 和 ulong 。
例如:
数值后缀显式定义了字面量的类型。后缀可以是下列小写或大写字母:
一般 U 和 L 后缀是很少需要的,因为 uint 、 long 和 ulong 总是可以推断出来或者从 int 类型隐式转换而来:
从技术上讲,后缀D是多余的,因为所有带小数点的字面量都会被推断为 double 类型。因此可以直接在数值字面量后加上小数点:
后缀 F 和 M 是最有用的,并应该在指定 float 或 decimal 字面量时使用。下面的语句不能在没有后缀 F 时进行编译,因为 4.5 会被认定为 double ,而 double 是无法隐式转换为 float 的:
同样的规则也适用于 decimal 字面量:
我们将在2.4.2节详细介绍数值转换的语义。
整数类型转换在目标类型能够表示源类型的所有可能值时是隐式转换,否则需要显式转换,例如:
double 能表示所有可能的 float 值,因此 float 能隐式转换为 double 。反之则必须是显式转换。
所有整数类型可以隐式转换为浮点类型:
反之,则必须是显式转换:
将浮点数转换为整数时,小数点后的数值将被截去而不会舍入。静态类 System.Convert 提供了在不同值类型之间转换的舍入方法(参见第6章)。
将大的整数类型隐式转换为浮点类型会保留数值部分,但是有时会丢失精度。这是因为浮点类型虽然拥有比整数类型更大的数值,但是有时其精度却比整数类型要小。以下代码用一个更大的数重复上述示例展示了这种精度丢失的情况:
所有的整数类型都能隐式转换为decimal类型,这是因为decimal可以表示所有可能的C#整数类型值。其他所有的数值类型转换为decimal或从decimal类型进行转换都必须是显式转换,因为这些转换要么数值可能超越边界,要么可能发生精度损失。
算术运算符( + 、 - 、 * 、 / 、 % )可应用于除8位和16位的整数类型之外的所有数值类型:
自增和自减运算符( ++ 、 -- )分别给数值类型加1或者减1,具体要将其放在变量之前还是之后则取决于需要得到变量在自增/自减之前的值还是之后的值,例如:
整数类型指 int 、 uint 、 long 、 ulong 、 short 、 ushort 、 byte 和 sbyte 。
整数类型的除法运算总是会舍去余数(向0舍入),用一个值为0的变量做除数将产生运行时错误( DivideByZeroException ):
用字面量或常量0做除数将产生编译时错误。
在运行时执行整数类型的算术运算可能会造成溢出。默认情况下,溢出会默默地发生而不会抛出任何异常,且其溢出行为是“周而复始”的。就像是运算发生在更大的整数类型上,而超出部分的进位就被丢弃了。例如,减小最小的整数值将产生最大的整数值:
checked 运算符的作用是,在运行时当整数类型表达式或语句超过相应类型的算术限制时不再默默地溢出,而是抛出 OverflowException 。 checked 运算符可在有 ++ 、 -- 、 + 、 - (一元运算符和二元运算符)、 * 、 / 和整数类型间显式转换运算符的表达式中起作用。溢出检查会带来微小的性能损失。
checked 运算符对 double 和 float 类型没有作用(它们会溢出为特殊的“无限”值,这会在后面介绍),对 decimal 类型也没有作用(这种类型总是会进行溢出检查)。
checked 运算符既可以包裹表达式也能够包裹语句块,例如:
在编译时打开checked开关(在Visual Studio中,可以在“Advanced Build Settings”中设置)将使程序在默认情况下对所有表达式都进行算术溢出检查。如果你只想禁用指定表达式或语句的溢出检查,可以用 unchecked 运算符。例如,下面的代码即使在编译时打开了checked开关也不会抛出异常:
无论是否打开了checked工程选项,编译时的表达式计算总会检查溢出,除非使用 unchecked 运算符。
C#支持以下的位运算符:
.NET 6将其他位运算操作添加到了 System.Numerics 命名空间下的 BitOperations 的类中(请参见6.10节)。
8位和16位整数类型是指 byte 、 sbyte 、 short 和 ushort 。这些类型自己并不具备算术运算符,所以C#隐式地将它们转换为所需的更大一些的类型。当试图把运算结果赋给一个小的整数类型时,会产生编译时错误:
在以上情况下, x 和 y 会隐式转换成 int 以便进行加法运算。因此运算结果也是 int ,它不能隐式转换回 short (因为这可能会造成数据丢失)。我们必须使用显式转换才能通过编译:
不同于整数类型,浮点类型还包含一些特殊的值,这些值在特定运算中需要特殊对待。这些特殊的值是 NaN (Not a Number,非数字)、+∞、-∞和-0。 float 和 double 类型包含表示 NaN 、+∞和-∞值的常量,其他的常量还有 MaxValue 、 MinValue 以及 Epsilon ,例如:
double 和 float 类型的特殊值的常量表如下:
非零值除以零的结果是无穷大:
零除以零,或无穷大减去无穷大的结果是 NaN :
使用比较运算符( == )时,一个 NaN 的值永远也不等于其他的值,甚至不等于其他的 NaN 值:
必须使用 float.IsNaN 或 double.IsNaN 方法来判断一个值是否为NaN:
但当使用 object.Equals 方法时,两个NaN却是相等的:
NaN在表示特殊值时很有用。在Windows Presentation Foundation(WPF)中, double.NaN 表示值为“Automatic”(自动),另一种表示这种值的方法是使用可空值类型(nullable,参见第4章)。还可以使用一个包含数值类型和一个额外字段的自定义结构体(参见第3章)来表示。
float 和 double 遵循IEEE 754格式类型规范。几乎所有的处理器都原生支持此规范。如果需要此类型行为的详细信息,可参考IEEE官方网站( http://www.ieee.org/ )。
double 类型常用于科学计算(例如,计算空间坐标)。 decimal 类型常用于金融计算和计算那些“人为”的而非真实世界的度量值。以下是这两种类型的不同之处:
(续)
float 和 double 在内部都是基于2来表示数值的。因此只有基于2表示的数值才能够精确表示。事实上,这意味着大多数有小数部分的字面量(它们都基于10)将无法精确表示。例如:
这就是为什么 float 和 double 不适合金融运算。相反, decimal 基于10,它能够精确表示基于10的数值(也包括它的因数、基于2和基于5的数值)。因为实数的字面量都是基于10的,所以 decimal 能够精确表示像0.1这样的数。然而, double 和 decimal 都不能精确表示那些基于10的循环小数:
这将会导致积累性的舍入误差:
这也将影响相等和比较操作: