在结构化文本(ST)编程中,对实数进行取整是常见需求,尤其在PLC控制逻辑里——比如将传感器采集的浮点温度值转换为整数索引、计算电机转速的整数倍频点、或对PID输出限幅后做离散化处理。TRUNC、ROUND、CEIL、FLOOR 四个函数名称相似,但行为截然不同。用错一个,轻则导致控制偏差累积,重则引发设备误动作。本文不讲理论推导,只聚焦你在编写ST代码时必须立刻知道的实操差异:每个函数怎么写、输入什么、输出什么、边界如何处理、何时该用哪一个。
一、先看结论:四函数核心行为对比
| 函数名 | 英文含义 | 对正数 3.7 的结果 |
对负数 -3.7 的结果 |
关键特征 |
|---|---|---|---|---|
TRUNC |
truncate(截断) | 3 |
-3 |
直接砍掉小数部分,不四舍五入,不向任何方向偏移 |
ROUND |
round to nearest integer(四舍五入) | 4 |
-4 |
距离最近的整数;若恰好在中间(如 2.5),按IEC 61131-3标准向偶数舍入(银行家舍入) |
CEIL |
ceiling(向上取整) | 4 |
-3 |
返回≥输入值的最小整数(向正无穷方向取整) |
FLOOR |
floor(向下取整) | 3 |
-4 |
返回≤输入值的最大整数(向负无穷方向取整) |
⚠️ 注意:所有函数均不修改原变量,而是返回新值;输入为 REAL 类型,输出为 INT 类型(部分PLC平台也支持 DINT 输出,需查手册确认返回类型)。
二、逐个深挖:每个函数的ST语法、执行逻辑与典型陷阱
1. TRUNC:最“暴力”的截断,零误差但不考虑数值倾向
语法:
Result := TRUNC(RealValue);
执行逻辑:
- 小数点后所有位全部丢弃,仅保留整数部分符号和绝对值。
- 等效于数学表达式:$\operatorname{trunc}(x) = \operatorname{sgn}(x) \cdot \lfloor |x| \rfloor$,其中 $\operatorname{sgn}(x)$ 是符号函数($x>0$ 时为 $1$,$x<0$ 时为 $-1$,$x=0$ 时为 $0$)。
关键验证示例(全部在ST中可直接运行):
TRUNC(0.0)→0TRUNC(5.999)→5TRUNC(-5.999)→-5TRUNC(100.0)→100
典型误用场景:
你想把模拟量 4–20 mA 对应的 0–100.0 % 实数百分比显示为整数百分比。若用 TRUNC(99.9) 得到 99,用户看到的是“99%”,但实际已接近满量程。此时更合理的是 ROUND 或 CEIL。
适用场景:
- 需要严格保持数值范围不越界的索引计算,例如数组下标:
Index := TRUNC(RealPos / 0.1);(确保Index不因舍入跳到下一个区间) - 单位换算中明确要求“向下对齐”,如将秒数
TotalSec := 3661.0;转为小时:Hours := TRUNC(TotalSec / 3600.0);→1(而非ROUND给出的1或2,此处结果相同,但逻辑更清晰)
2. ROUND:最贴近日常认知的“四舍五入”,但有隐藏规则
语法:
Result := ROUND(RealValue);
执行逻辑(IEC 61131-3 标准):
- 不是简单“逢5进1”,而是采用银行家舍入法(Round Half to Even):
- 若小数部分
< 0.5,向下取整; - 若小数部分
> 0.5,向上取整; - 若小数部分
== 0.5,则向最近的偶数取整。
- 若小数部分
为什么用银行家舍入?
避免统计偏差:对大量 .5 数据统一向上舍入,会导致整体结果系统性偏高。向偶数舍入使“进”和“舍”概率趋近平衡。
关键验证示例:
ROUND(1.5)→2(2 是偶数)ROUND(2.5)→2(2 是偶数)ROUND(3.5)→4(4 是偶数)ROUND(-1.5)→-2(-2 是偶数)ROUND(-2.5)→-2(-2 是偶数)ROUND(0.5)→0(0 是偶数)
典型误用场景:
你在做温度报警阈值判断:设定 AlarmTemp := ROUND(Setpoint + 0.5);。若 Setpoint 是 24.5,则 24.5 + 0.5 = 25.0 → ROUND(25.0) = 25,正确;但若 Setpoint 是 24.0,24.0 + 0.5 = 24.5 → ROUND(24.5) = 24(偶数),报警值反比设定值低了1℃。此时应直接用 CEIL(Setpoint + 0.5)。
适用场景:
- 用户界面显示(如HMI上显示“当前转速:
ROUND(RPM_Real)rpm”) - 统计类计算(如平均电流
AvgCurrent := ROUND((I1 + I2 + I3) / 3.0);) - 任何需要“感知上最接近整数”的场合
3. CEIL:无条件向上,用于“至少达到某值”
语法:
Result := CEIL(RealValue);
执行逻辑:
- 返回大于或等于
RealValue的最小整数。 - 数学定义:$\lceil x \rceil = \min\{n \in \mathbb{Z} \mid n \geq x\}$。
关键验证示例:
CEIL(0.1)→1CEIL(0.0)→0CEIL(-0.1)→0(因为0 ≥ -0.1,且没有比0更小的整数满足该条件)CEIL(-3.0)→-3CEIL(-3.0001)→-3?错!→-3是-3.0001的上界吗?不,-3 > -3.0001成立,但-4 < -3.0001,所以-3是最小满足条件的整数 → 正确结果是-3。
✅ 再验证:CEIL(-3.999)→-3(因为-3 ≥ -3.999,而-4 < -3.999)
典型误用场景:
你设计一个批次计数器,每 2.3 升液体触发一次计数。当前累计 Volume := 6.8;。若用 CEIL(6.8 / 2.3) → CEIL(2.956...) = 3,正确;但若误用 ROUND → 3(巧合相同),而 Volume := 4.6 时,4.6 / 2.3 = 2.0 → ROUND(2.0) = 2,CEIL(2.0) = 2,仍一致。真正风险在 Volume := 4.599:4.599 / 2.3 ≈ 1.9996 → ROUND = 2,CEIL = 2;但 Volume := 2.301 → 2.301 / 2.3 ≈ 1.0004 → ROUND = 1,CEIL = 2 —— 这才是 CEIL 的价值:它保证“只要超过一个完整单位,就计1次”。
适用场景:
- 批次/分组数量计算:
Groups := CEIL(TotalWeight / MaxPerGroup); - 报警延时启动:
DelayCycles := CEIL(RequiredDelay_ms / CycleTime_ms);(确保延时不少于设定值) - 内存分配块数:
Blocks := CEIL(DataSize_bytes / BlockSize_bytes);
4. FLOOR:无条件向下,用于“最多不超过某值”
语法:
Result := FLOOR(RealValue);
执行逻辑:
- 返回小于或等于
RealValue的最大整数。 - 数学定义:$\lfloor x \rfloor = \max\{n \in \mathbb{Z} \mid n \leq x\}$。
关键验证示例:
FLOOR(0.9)→0FLOOR(0.0)→0FLOOR(-0.1)→-1(因为-1 ≤ -0.1,而0 > -0.1)FLOOR(-2.0)→-2FLOOR(-2.0001)→-3(因为-3 ≤ -2.0001,而-2 > -2.0001)
典型误用场景:
你在做PWM占空比映射:REAL 值 0.0–1.0 映射到 INT 寄存器 0–255。若写 DutyInt := FLOOR(DutyReal * 255.0);,当 DutyReal = 1.0 → 255.0 → 255,正确;但若 DutyReal = 0.9999 → 254.9745 → 254,丢失了最高精度。此时应改用 ROUND(DutyReal * 255.0) 或加 + 0.5 后 TRUNC:TRUNC(DutyReal * 255.0 + 0.5)。
适用场景:
- 安全区间判断:
ZoneID := FLOOR(Position_mm / 1000.0);(每1000mm一个区,位置999mm属0区,1000mm起属1区) - 周期性任务调度:
TaskID := FLOOR(Elapsed_ms / TaskPeriod_ms);(确定当前处于第几个周期) - 整数除法余数提取(配合乘法):
Remainder := RealValue - (FLOOR(RealValue / Divisor) * Divisor);
三、实战组合技:单函数不够用?试试这3种安全写法
▶ 场景1:需要“传统四舍五入(逢5进1)”,而非银行家舍入
IEC标准 ROUND 不符合某些老系统习惯。安全替代写法:
// 向上偏移0.5后TRUNC,等效传统四舍五入(仅适用于正数)
TraditionalRound := TRUNC(RealValue + 0.5);
// 兼容正负数的通用写法(推荐)
IF RealValue >= 0.0 THEN
TraditionalRound := TRUNC(RealValue + 0.5);
ELSE
TraditionalRound := TRUNC(RealValue - 0.5);
END_IF;
▶ 场景2:REAL 转 INT 时防止溢出(ST中无自动类型保护)
TRUNC(1E10) 在32位 INT 范围外会回绕(如变成 -2147483648)。务必加保护:
IF RealValue > 32767.0 THEN
SafeInt := 32767;
ELSIF RealValue < -32768.0 THEN
SafeInt := -32768;
ELSE
SafeInt := TRUNC(RealValue);
END_IF;
▶ 场景3:实现“向上取整到指定精度”(如精确到0.1)
需求:3.14159 → 3.2(向上到十分位)。
Precision := 0.1; // 目标精度
Scaled := RealValue / Precision; // 3.14159 / 0.1 = 31.4159
RoundedUp := CEIL(Scaled) * Precision; // CEIL(31.4159)=32 → 32*0.1=3.2
四、避坑清单:ST取整中90%程序员踩过的雷
-
❌ 在
CASE语句中直接用ROUND(RealVar)作选择器:CASE ROUND(RealVar) OF ...—— 若RealVar是2.5,ROUND结果是2,但人脑预期是3,逻辑断裂。应提前赋值并注释:RoundedVal := ROUND(RealVar); // 使用银行家舍入 CASE RoundedVal OF -
❌ 对负数用
TRUNC代替FLOOR:TRUNC(-2.8)是-2,FLOOR(-2.8)是-3—— 完全不同!别凭直觉。 -
❌ 忘记类型匹配:
ROUND输出INT,但你声明Result : DINT;—— 多数PLC会静默转换,但某些平台报错。显式转换更安全:Result := INT_TO_DINT(ROUND(RealVal)); -
❌ 在循环中高频调用取整函数:
ROUND比TRUNC计算开销略大(涉及比较和分支),若性能敏感(如高速运动控制),优先用TRUNC或FLOOR+ 偏移。 -
❌ 把
CEIL当ROUND用:CEIL(2.1)=3,远超预期。记住口诀:CEIL是“天花板”,FLOOR是“地板”,TRUNC是“一刀切”,ROUND是“找邻居”。
五、终极决策树:遇到实数取整,3秒选对函数
当你写下 X := ???(Y);,按顺序问自己:
-
是否需要“无条件向上”,比如“至少分配1块内存”或“延时不能少于设定值”?
→ 选CEIL(Y) -
是否需要“无条件向下”,比如“最多运行到该区段末尾”或“周期计数不跨提前”?
→ 选FLOOR(Y) -
是否只是“去掉小数部分”,且不希望任何舍入影响原始区间归属(如数组索引)?
→ 选TRUNC(Y) -
是否面向人眼显示、统计汇总,或需要最接近的整数代表值?
→ 选ROUND(Y)(默认银行家舍入)
→ 若必须传统四舍五入,用TRUNC(Y + 0.5)(正数)或前述通用写法
其余情况,回归问题本质:你到底想表达“≥”、“≤”、“截断”还是“最近”?答案唯一。

暂无评论,快来抢沙发吧!