Menu

C# 数据类型:int、double、decimal、char、bool 及其取值范围

C# 每种内置类型的大小、取值范围和字面量后缀,值类型与引用类型的区别,为什么金额要用 decimal,整数溢出与 checked,以及默认值。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

C# 是静态类型语言:每个变量、字段和表达式都有一个在编译时就已知的类型。这门语言有一组拥有专属关键字的内置类型(int、double、string 等)。每个关键字都是 System 命名空间中某个类型的别名,所以 int 和 System.Int32 是同一个类型,string 和 System.String 也是同一个类型。

内置类型

类型.NET 类型大小范围或精度字面量
boolBoolean1 字节true 或 falsetrue
byteByte1 字节0 至 255200
sbyteSByte1 字节-128 至 127-5
shortInt162 字节-32,768 至 32,7671200
ushortUInt162 字节0 至 65,53560000
intInt324 字节-2,147,483,648 至 2,147,483,64742
uintUInt324 字节0 至 4,294,967,29542u
longInt648 字节约 ±9.2 × 10^1842L
ulongUInt648 字节0 至约 1.8 × 10^1942ul
floatSingle4 字节约 ±3.4 × 10^38,约 6 至 9 位有效数字2.5f
doubleDouble8 字节约 ±1.8 × 10^308,约 15 至 17 位有效数字2.5 或 2.5d
decimalDecimal16 字节约 ±7.9 × 10^28,28 至 29 位有效数字2.5m
charChar2 字节一个 UTF-16 码元,U+0000 至 U+FFFF'A'
stringString引用任意长度的文本,不可变"hello"
objectObject引用所有类型的基类new object()

不带后缀的整数字面量是 int;如果它大到 int 装不下,就是 uint、long、ulong 中第一个能容纳它的类型。带小数点的字面量是 double。这就是为什么 float f = 2.5; 和 decimal d = 2.5; 无法编译:字面量是 double,而 double 不能隐式转换为 float 或 decimal。加上后缀即可:2.5f、2.5m。

每种数值类型都以常量形式提供了自己的上下限:

输出:

byte:  0 to 255
short: -32768 to 32767
int:   -2147483648 to 2147483647
long:  -9223372036854775808 to 9223372036854775807
decimal max: 79228162514264337593543950335
double max:  1.798E+308
sizes: int 4, long 8, double 8, char 2
255 161 3000000000

实际代码中最常用的是其中四种:int 用于计数和索引,long 用于 ID 和大额总数,double 用于测量值和数学计算,decimal 用于金额。

decimal 与 double

double(以及 float)以二进制形式存储数字。大多数十进制小数,包括 0.1,都没有精确的二进制形式,所以存储的是最接近的可表示值,小误差会不断累积。decimal 存储的是一个十进制整数加一个小数位数,所以 0.1 就是精确的 0.1。

输出:

False
True
0.30000000000000004
0.3
double sum == 1.0: False
decimal sum == 1.0: True
Total: 59.97
Split three ways: 19.99

金额、价格、税费以及任何人们会手工相加并核对的数字,都用 decimal。它会保留输入中末尾的零(1.50m 打印为 1.50),有 28 到 29 位有效数字。

物理测量、统计、几何和游戏用 double。它快好几倍,内存只占一半,取值范围也极大。永远不要用 == 比较两个计算得到的 double,而是把它们的差与一个容差比较:Math.Abs(x - y) < 1e-9。

decimal 也不是万能的:1m / 3m 的结果仍然是 0.3333333333333333333333333333。另外,double 运算除以零不会抛出异常(结果是无穷大,0.0 / 0.0 则是 NaN),而 decimal 和整数除以零会抛出 DivideByZeroException。

整数溢出与 checked

默认情况下,超出类型范围的整数运算会悄无声息地回绕。编译器能发现常量表达式中的溢出,但值来自变量时就发现不了:

输出:

-2147483648
OverflowException
-1294967296
2147483648

checked(...) 或 checked { ... } 块会把溢出变成 OverflowException。你也可以用 <CheckForOverflowUnderflow>true</CheckForOverflowUnderflow> 为整个项目开启溢出检查。用常量写 int x = int.MaxValue + 1; 会产生编译错误(CS0220),因为编译器能看出溢出。

最后一行展示了大多数溢出 bug 的修复方法:在运算之前先转换为更宽的类型。写成 (long)(max + 1) 是错的,因为执行强制转换时,加法早已回绕了。

值类型与引用类型

C# 的每种类型要么是值类型,要么是引用类型,这个区别决定了赋值时会发生什么。

  • 值类型:所有数值类型、bool、char、DateTime、枚举以及每个 struct。变量直接包含数据;赋值时复制数据。
  • 引用类型:string、数组、object 以及每个 class。变量包含指向对象的引用;赋值时复制引用,所以两个变量看到的是同一个对象。

输出:

a=10 b=99
first[0]=99
s1=cat s2=cats

string 是引用类型,但实际用起来像值:它不可变,而且 == 比较的是文本,而不是引用。更多关于用户自定义值类型的内容,见 结构体。

char 和 bool

char 是一个 UTF-16 码元,用单引号书写。它的底层是数值类型,所以可以转换为 int,也支持算术运算:

输出:

66
C
True
True
7
b
True

bool 只有两个值,不能与数字互相转换:if (count) 是编译错误,而不是判断是否非零。请写 if (count != 0)。基本多文种平面之外的字符(比如大多数 emoji)占用两个 char 值(一个代理对),所以 string 的 Length 统计的是 UTF-16 码元,而不是可见字符。

默认值

字段、数组元素和 default(T) 会得到该类型的默认值:数字是零,bool 是 false,char 是 '\0',引用类型是 null。局部变量没有默认值,使用之前必须先赋值。

输出:

0
0
False
True
0001-01-01
0,0,0

从 C# 7.1 开始,当编译器能推断出类型时,你可以直接写不带类型的 default 字面量(int x = default;)。值类型不能为 null,int?(可空值类型)正是为此而生;见 可空类型。

本机大小整数和大整数

还有两种用于特殊场景的数值类型。nint 和 nuint(C# 9)是与指针同样大小的整数,根据平台是 32 位或 64 位,用于互操作和底层代码。System.Numerics.BigInteger 可以保存任意大小的整数,代价是速度:

using System.Numerics;
BigInteger factorial = 1;
for (int i = 2; i <= 30; i++) factorial *= i;
// 265252859812191058636308480000000

常见问题

C# 有哪些数据类型?

内置类型包括整数类型(sbyte、byte、short、ushort、int、uint、long、ulong)、浮点类型 float 和 double、用于精确十进制运算的 decimal、表示一个 UTF-16 字符的 char、bool,以及引用类型 string 和 object。每个关键字都是一个 .NET 类型的别名:int 就是 System.Int32,string 就是 System.String。

C# 中表示金额应该用 decimal 还是 double?

用 decimal。double 存储的是二进制小数,所以像 0.1 这样的值无法精确表示,累加起来会产生偏差(0.1 + 0.2 == 0.3 的结果是 false)。decimal 存储的是十进制数字,有 28 到 29 位有效数字,能得到会计期望的结果。double 更快,取值范围也大得多,适合科学计算、图形和测量。

C# 中 int 的最大值是多少?

int.MaxValue 是 2,147,483,647,int.MinValue 是 -2,147,483,648(32 位有符号整数)。给 int.MaxValue 加 1 会悄悄回绕成 int.MinValue,除非代码在 checked 上下文中运行,那时会抛出 OverflowException。更大的值请用 long(最大约 922 亿亿)。

C# 中 float 和 double 有什么区别?

float 是 32 位,约有 6 到 9 位有效数字;double 是 64 位,约有 15 到 17 位有效数字。浮点字面量默认是 double,所以 float 需要加 f 后缀:float speed = 2.5f;。除非内存或某个 API(比如 Unity 的 Vector3)要求用 float,否则请用 double。

C# 中值类型和引用类型有什么区别?

值类型变量(int、double、bool、DateTime、任何 struct)直接保存数据本身,赋值时会复制数据。引用类型变量(string、数组、任何 class)保存的是指向堆上对象的引用,赋值时复制的是引用,所以两个变量可以指向同一个对象。

Coddy 编程语言插图

和 Coddy 一起学编程

开始