装数字的十二只杯子

上一篇我们知道了「数据类型就是装数据的杯子」。这一篇专门讲其中的一大类——数值类型,也就是「能装数字」的类型。把 8 种整数 + 2 种小数吃透,上位机读数据、换算工程量就全通了。

本篇目标:看到 intdoublebyte 这些词,能马上说出——是整数还是小数、大概能装多大、上位机里用在哪。


一、先认识两个基础词:位 和 字节

理解数值类型,绕不开两个词。它们不复杂,就是「存储空间」的度量单位。

  • (bit):电脑里最小的单位,只能存 0 或 1,就像一盏只能「开」或「关」的灯。
  • 字节(byte):把 8 个位打包成一组,就是 1 个字节。
位和字节到底啥关系?

把 8 盏灯排成一排,每盏灯都是 0 或 1,这一整排 8 盏灯就是一个字节。

  • 1 个字节 = 8 个位,能表示 2 的 8 次方 = 256 种组合。
  • 2 个字节 = 16 个位,能表示 2 的 16 次方 = 65536 种组合。
  • 4 个字节 = 32 个位,能表示 2 的 32 次方 = 约 42 亿种组合。

所以字节越多,能装的数越大。下面所有整型的范围,都是这么算出来的。


二、为什么整数要分这么多?(8 种整型)

你可能会问:装个整数而已,怎么搞出 byte / short / int / long 一大堆?答案是两条理由。

理由一:内存大小不同

上位机里有些数据很小(比如阀门开度 0~255),有些很大(比如累计电量、时间戳)。

如果所有整数都用最大的 8 字节来装,就会浪费内存。所以 C# 准备了不同大小的「杯子」,小的用小杯,大的用大杯,内存才不浪费。

理由二:有符号 和 无符号

同样是 1 个字节:

  • 无符号(前面没有 S):只装正数,比如 byte 是 0 到 255。
  • 有符号(前面带 S):留一半空间给负数,比如 sbyte 是 -128 到 127。

一句话:有 S 能负数没 S 全正数


三、8 种整型逐个看

下面这张表就是整型全家桶,从最小到最大排好。不用背,先混个脸熟,用到时回来翻。

类型 名字 范围 占字节
sbyte 有符号字节 -128 到 127 1
byte 无符号字节 0 到 255 1
short 有符号短整型 -32768 到 32767 2
ushort 无符号短整型 0 到 65535 2
int 有符号整型 约 -21 亿 到 21 亿 4
uint 无符号整型 0 到 约 42 亿 4
long 有符号长整型 约 -9.2 后面 18 个零 8
ulong 无符号长整型 0 到 约 1.8 后面 19 个零 8

最小的一对:sbyte 和 byte

1
2
3
4
5
sbyte temperature = -10;   // 温度可以是负的
byte valveOpen = 200; // 阀门开度 0~255,没负数

Console.WriteLine(temperature);
Console.WriteLine(valveOpen);
上位机里用在哪
  • byte 最常出现在串口、Modbus 报文里,一个字节一个字节地处理。
  • sbyte 可以装带负号的温度、角度这类小量。

稍大一点:short 和 ushort

1
2
3
4
5
short  errorCode = -5000;     // 错误码可以是负数
ushort deviceId = 60000; // 设备编号从 0 起算

Console.WriteLine(errorCode);
Console.WriteLine(deviceId);
上位机里用在哪

老式 PLC(如西门子 S7-200)的单个模拟量通道就是 16 位(2 字节),读上来正好用 short 接。

最常用的主力:int 和 uint

int 是整型里最常用的,绝大多数「普通整数」都用它。没特殊理由,整数就默认写 int

1
2
3
4
5
int  productCount = 12345;     // 今天生产了 12345 个
uint totalPower = 4000000000; // 累计用电量,不会为负

Console.WriteLine(productCount);
Console.WriteLine(totalPower);
上位机里用在哪
  • 计件数、扫码枪的物料编号、生产班次号,基本都写 int
  • 确认不会为负的(数量、用电量),用 uint 表达更清楚。

最大的:long 和 ulong

1
2
3
4
5
long  timestamp = 1735872000123L;  // 时间戳,非常大,加 L 后缀
ulong fileSize = 5000000000UL; // 文件 5G 字节大小

Console.WriteLine(timestamp);
Console.WriteLine(fileSize);

看数字后面的 LUL 没?没加的话,C# 默认按 int 处理,数字一大就报错。加后缀就是告诉电脑「这是 long / ulong」。

上位机里用在哪
  • 时间戳(从 1970 年 1 月 1 日到现在的毫秒数)必须用 long
  • 累计流量、开机总时长这类「特别大」的数,也要用 long

四、带小数的浮点型:float 和 double

整数只能装 1、2、3 这种干干净净的数,要装 3.14、25.6 这种带小数点的,就得用浮点型。浮点型有 2 个常用的,外加 1 个算钱的。

类型 名字 大概范围 精度 后缀
float 单精度浮点 约 ±3.4 后面 38 个零 6~7 位有效数字 必须加 F
double 双精度浮点 约 ±1.79 后面 308 个零 15~16 位有效数字 不用加
decimal 高精度小数 约 ±7.9 后面 28 个零 28~29 位有效数字 必须加 M

float:必须加 F

1
2
3
4
5
float temperature = 36.5F;   // 加 F,表示这是 float
float voltage = 3.3F; // 电压 3.3V

Console.WriteLine(temperature);
Console.WriteLine(voltage);

float 的数字后面必须加 F,比如 36.5F。不加 F,C# 会把它当成 double,塞进 float 里就报错。

double:默认的小数类型

1
2
3
4
5
double pressure = 101.325;   // 什么都不用加
double ratio = 0.6180339887;

Console.WriteLine(pressure);
Console.WriteLine(ratio);
上位机里最常用的换算

很多 PLC 的模拟量原始值是 0~27648,要换算成工程量,就用 double

1
2
3
4
double rawValue  = 13824;              // PLC 读上来的原始值
double realValue = rawValue / 27648.0 * 100.0; // 注意用了 .0

Console.WriteLine("实际温度:" + realValue + " °C");

除数和倍数写成 27648.0100.0(带小数点),结果才保证是小数。输出会是「实际温度:50 °C」。

decimal:算钱才用它

decimal 不在这次截图里,但先记个脸熟:金额、计费这种「一分都不能差」的场景,用 decimal 而不是 float / double。它更精确,代价是运算稍慢、占内存稍多。

1
2
decimal price = 19.99M;    // 加 M 后缀
Console.WriteLine(price);

五、一个必须知道的坑:溢出

「溢出」就是往小杯子里硬倒大桶水,水会漫出来。数据也一样——超过了类型能装的范围,数字会变错。

1
2
3
4
5
6
7
8
// byte 最大只能到 255
byte b = 200;
b = 200 + 100; // 300 超过了 255,会报错(编译时就能发现)

// 更隐蔽的是运行时溢出
int max = 2147483647;
int result = max + 1; // 会变成 -2147483648,绕回到最负数
Console.WriteLine(result);

整数选型时,先想想「这个数最大会到多少」。计件数可能到 21 亿吗?可能就要用 long。这就是选类型的第一原则:宁大勿小,装得下比省内存更重要(尤其新手阶段)。


六、小结

这一篇我们学了数值类型全家桶:

  • 8 种整型:sbyte / byte / short / ushort / int / uint / long / ulong,越往后越大。
  • 2 种浮点:float(加 F)、double(默认)。
  • 1 个算钱的:decimal(加 M)。

记一个口诀:

有 S 能负数,没 S 全正数小数看后缀(F 是 float,M 是 decimal,double 不用加)。

下一步建议:

  1. 在 Visual Studio 里把每种整型都声明一次、打印一次,看范围和输出。
  2. 故意给 byte 赋一个 300,看编译器报什么错。
  3. 下一篇我们学「变量与赋值」,把这些类型真的装进带名字的盒子里用起来。

七、能跑起来的小练习

点我看完整练习代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using System;

class Program
{
static void Main()
{
long ts = 1735872000123L; // 时间戳
double temp = 25.6; // 温度
int raw = 13824; // 模拟量原始值
uint pulse = 123456; // 累计脉冲
byte open = 200; // 阀门开度

double real = raw / 27648.0 * 100.0;

Console.WriteLine("时间戳:" + ts);
Console.WriteLine("温度:" + temp + " °C");
Console.WriteLine("实际温度:" + real + " °C");
Console.WriteLine("累计脉冲:" + pulse);
Console.WriteLine("阀门开度:" + open);
}
}

期望输出:

1
2
3
4
5
时间戳:1735872000123
温度:25.6 °C
实际温度:50 °C
累计脉冲:123456
阀门开度:200