文章目录

ST实数取整:TRUNC、ROUND、CEIL、FLOOR在ST中的具体区别

发布于 2026-03-20 13:12:41 · 浏览 4 次 · 评论 0 条

在结构化文本(ST)编程中,对实数进行取整是常见需求,尤其在PLC控制逻辑里——比如将传感器采集的浮点温度值转换为整数索引、计算电机转速的整数倍频点、或对PID输出限幅后做离散化处理。TRUNCROUNDCEILFLOOR 四个函数名称相似,但行为截然不同。用错一个,轻则导致控制偏差累积,重则引发设备误动作。本文不讲理论推导,只聚焦你在编写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)0
  • TRUNC(5.999)5
  • TRUNC(-5.999)-5
  • TRUNC(100.0)100

典型误用场景
你想把模拟量 4–20 mA 对应的 0–100.0 % 实数百分比显示为整数百分比。若用 TRUNC(99.9) 得到 99,用户看到的是“99%”,但实际已接近满量程。此时更合理的是 ROUNDCEIL

适用场景

  • 需要严格保持数值范围不越界的索引计算,例如数组下标:Index := TRUNC(RealPos / 0.1);(确保 Index 不因舍入跳到下一个区间)
  • 单位换算中明确要求“向下对齐”,如将秒数 TotalSec := 3661.0; 转为小时:Hours := TRUNC(TotalSec / 3600.0);1(而非 ROUND 给出的 12,此处结果相同,但逻辑更清晰)

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);。若 Setpoint24.5,则 24.5 + 0.5 = 25.0ROUND(25.0) = 25,正确;但若 Setpoint24.024.0 + 0.5 = 24.5ROUND(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)1
  • CEIL(0.0)0
  • CEIL(-0.1)0(因为 0 ≥ -0.1,且没有比 0 更小的整数满足该条件)
  • CEIL(-3.0)-3
  • CEIL(-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,正确;但若误用 ROUND3(巧合相同),而 Volume := 4.6 时,4.6 / 2.3 = 2.0ROUND(2.0) = 2CEIL(2.0) = 2,仍一致。真正风险在 Volume := 4.5994.599 / 2.3 ≈ 1.9996ROUND = 2CEIL = 2;但 Volume := 2.3012.301 / 2.3 ≈ 1.0004ROUND = 1CEIL = 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)0
  • FLOOR(0.0)0
  • FLOOR(-0.1)-1(因为 -1 ≤ -0.1,而 0 > -0.1
  • FLOOR(-2.0)-2
  • FLOOR(-2.0001)-3(因为 -3 ≤ -2.0001,而 -2 > -2.0001

典型误用场景
你在做PWM占空比映射:REAL0.0–1.0 映射到 INT 寄存器 0–255。若写 DutyInt := FLOOR(DutyReal * 255.0);,当 DutyReal = 1.0255.0255,正确;但若 DutyReal = 0.9999254.9745254,丢失了最高精度。此时应改用 ROUND(DutyReal * 255.0) 或加 + 0.5TRUNCTRUNC(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:REALINT 时防止溢出(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.141593.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 ... —— 若 RealVar2.5ROUND 结果是 2,但人脑预期是 3,逻辑断裂。应提前赋值并注释:

    RoundedVal := ROUND(RealVar); // 使用银行家舍入
    CASE RoundedVal OF
  • ❌ 对负数用 TRUNC 代替 FLOORTRUNC(-2.8)-2FLOOR(-2.8)-3 —— 完全不同!别凭直觉。

  • ❌ 忘记类型匹配:ROUND 输出 INT,但你声明 Result : DINT; —— 多数PLC会静默转换,但某些平台报错。显式转换更安全:Result := INT_TO_DINT(ROUND(RealVal));

  • ❌ 在循环中高频调用取整函数:ROUNDTRUNC 计算开销略大(涉及比较和分支),若性能敏感(如高速运动控制),优先用 TRUNCFLOOR + 偏移。

  • ❌ 把 CEILROUND 用:CEIL(2.1) = 3,远超预期。记住口诀:CEIL 是“天花板”,FLOOR 是“地板”,TRUNC 是“一刀切”,ROUND 是“找邻居”。


五、终极决策树:遇到实数取整,3秒选对函数

当你写下 X := ???(Y);,按顺序问自己:

  1. 是否需要“无条件向上”,比如“至少分配1块内存”或“延时不能少于设定值”?
    → 选 CEIL(Y)

  2. 是否需要“无条件向下”,比如“最多运行到该区段末尾”或“周期计数不跨提前”?
    → 选 FLOOR(Y)

  3. 是否只是“去掉小数部分”,且不希望任何舍入影响原始区间归属(如数组索引)?
    → 选 TRUNC(Y)

  4. 是否面向人眼显示、统计汇总,或需要最接近的整数代表值?
    → 选 ROUND(Y)(默认银行家舍入)
    → 若必须传统四舍五入,用 TRUNC(Y + 0.5)(正数)或前述通用写法

其余情况,回归问题本质:你到底想表达“≥”、“≤”、“截断”还是“最近”?答案唯一。

评论 (0)

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

扫一扫,手机查看

扫描上方二维码,在手机上查看本文