c#数值类型详解

401 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情

前言

本文总结了C#中的值类型,特别是数值类型的细节问题。虽然数值类型的使用是一个基础知识,但语言特性中有一些不常见的点也是值得了解的。

C#预定义值类型包含

  • 数值类型
    • 有符号整数类型: sbyte, short, int, long
    • 无符号整数类型: byte, ushort, uint, ulong
    • 实数类型:float, dobule, decimal
  • 逻辑类型
    • bool
  • 字符类型
    • char

预定义类型是C# Framework中特定类型的别名

  • 例如 int 其实是 System.Int32 类型
  • 它们只是使用的语法不太一样,其实int可以看做是编译器的语法糖

除decimal之外的值类型都是原子类型(Primitive type)

  • 原子类型在编译之后的代码中有直接的指令支持,并且通常有处理器的直接支持。
  • 例如float类型,编译后为IEEE浮点数编码。
  • System.IntPtr和System.UIntPtr也是原子类型。

数值类型范围

  • sbyte/byte: 8位
  • short/ushort: 16位
  • int/uint: 32位
  • long/ulong: 64位
  • float: 32位
  • double: 64位
  • decimal: 128位

C#7引入的数字下划线分隔

  • 可以在数字字面量任意位置插入下划线,这是为了增强数字的可读性
  • 例如: int some_larg_number = 1_234_567;

数字字面量的类型推断

  • 在没有显示指定数字字面量的后缀时,编译器会对其类型进行推断
  • 编译器默认推断数字字面量的类型为double或整数类型。
    • 如果字面量包含小数点,或者指数符号E,则推断为double
    • 否则,字面量类型按照这个序列去尝试推断:int, uint, long, ulong,当某类型可以容纳字面量的值时,则推断为该类型。
    • 例如:1.0的类型为double,1的类型为int,0XF0000000的类型为uint

数字后缀

  • 后缀添加在数字字面量后面,明确的指定字面量的类型,大小写均可

  • 后缀包含F, D, M, U, L, UL:

    • float f = 1.0f;
    • double d = 1D;
    • decimal d = 1.0M;
    • uint i = 1U;
    • long i = 1L;
    • ulong i = 1UL;
  • 后缀 U 和 L 几乎用不上,因为 uint, long, ulong总是能被推断出来,或者从int隐式转换

  • 后缀 D 也是多余的,因为只要有小数点,就能推断为double,而小数点总是可以使用的,例如 2.0

  • 后缀 F 和 M 是最有用的。比如对于float类型,必须使用后缀F才能指定。例如: float f = 3.14f; 如果不使用后缀F,则3.14肯定就推断为double了,且不能从double隐式转换到float,因此会造成一个编译错误。

溢出和溢出检测

整数类型计算造成溢出时默认情况不会抛出异常,使用checked操作符可以检查溢出,当溢出发生时抛出OverflowException异常。例如:

int a = 1234567;
int b = 1234567;
int c = checked(a * b); //检查单个表达式

//检查一个块中的所有表达式
checked
{
    c = a * b;
    c = a * a;    
}

  • 注意:浮点类型是不支持 checked 操作符的
  • 通过编译器选项可以默认打开checked,即不需要显示使用checked操作符即可以进行检查。这种情况下,如果想要对于个别表达式不进行检查,可以使用unchecked操作符。
  • 常量表达式总会进行checked检查,如果不想检查,可以使用unchecked。

特殊浮点数值

特殊浮点数值常量

  • NaN: double.NaN 和 float.NaN
  • 正无穷大 double.PositiveInfinity和float.PositiveInfinity
  • 负无穷大 double.NegativeInfinity和float.NegativeInfinity
  • 负0:-0.0 和 -0.0f

除0的结果

非0数值除以0得到无穷大

  • 1.0/0.0 和 -1.0/-0.0 得到正无穷大
  • -1.0/0.0 和 1.0/-0.0 得到负无穷大

0除0

  • 0.0/0.0 得到 NaN

无穷大相减

  • (1.0/0.0) - (1.0./0.0) 得到NaN

NaN比较

  • 使用 == 和 NaN 比较,结果总是False
  • 甚至 == 的两端都是NaN,结果也是False,例如: 0.0/0.0 == double.NaN 结果为False
  • 使用object.Equals比较时,两个NaN是相等的,例如: object.Equals(0.0/0.0, double.NaN);结果为True

测试NaN

使用 double.IsNaN() 和 float.IsNaN()

NaN的用处

可以使用NaN表示一个特定值,表示未设置等情况,使用IsNaN测试。

浮点数误差

  • float和double内部表达都是基于2进制,因此它们只能精确表示基于2进制的数值。而很多基于10进制的小数则不能精确表示。而decimal类型是基于10进制的,因此可以精确表示10进制数。
  • 由于误差的存在,浮点数之间的比较要特别小心。一般会使用一个容错值进行比较。