在电气自动化领域,PLC(可编程逻辑控制器)程序中处理浮点数比较是一个高频但极易出错的操作。尤其在使用结构化文本(Structured Text,ST)语言编程时,新手常直接写 IF A = B THEN ... 来判断两个实数是否相等。这种写法在绝大多数工业现场会导致逻辑失效——不是偶尔跳过动作,就是误触发报警,甚至引发设备误停。根本原因在于:浮点数在计算机中无法精确表示大多数十进制小数,直接用 = 比较等于在比“近似值的近似值”。
为什么不能用 = 直接比较浮点数?
PLC 的 ST 语言遵循 IEC 61131-3 标准,其 REAL 类型通常对应 IEEE 754 单精度(32 位)浮点格式。该格式将一个数拆分为符号位、指数位和尾数位,用二进制近似表示十进制数值。例如:
- 十进制
0.1在二进制中是无限循环小数:0.00011001100110011...₂ - PLC 只能截取有限位(单精度约 7 位有效十进制数字),因此存储的实际值是
0.10000000149011612(误差约1.5×10⁻⁹)
当执行连续运算(如 A := A + 0.1; 循环 10 次)后,A 的理论值应为 1.0,但实际存储值可能是 0.99999994 或 1.00000012。此时 IF A = 1.0 THEN 判断必然为 FALSE,即使人眼看来“完全相等”。
IEC 61131-3 标准明确指出:= 运算符对 REAL 类型执行的是逐位比特比较,而非数学意义上的相等。只要两个 REAL 变量的二进制编码存在哪怕 1 个比特差异,结果就是 FALSE。
正确方案:用绝对误差阈值替代直接相等
工业实践中唯一可靠的方法是:判断两数之差的绝对值是否小于一个预设的小正数(即“容差”,tolerance)。标准写法正是题目所给形式:
IF ABS(A - B) < 0.001 THEN
// 执行相等时的动作
END_IF;
该逻辑的数学含义是:
若
A与B的差距不超过0.001,则认为它们在工程允许范围内“相等”。
此方法不依赖浮点数的精确表示,只关心实际物理意义下的偏差是否可接受。
如何选择合适的容差值?——3 个关键原则
容差不是越小越好,也不是随便填 0.001 就万事大吉。选错容差会引入新问题:过大导致误判(把明显不同的值当作相等),过小则失去规避浮点误差的意义。需综合以下三点确定:
-
匹配物理量的测量精度
例如:压力传感器量程 0–10 bar,精度 ±0.02 bar,则容差不应小于0.02;若设为0.0001,程序永远无法触发——因为真实读数本身就在±0.02范围内跳动。 -
考虑运算累积误差
若A是由多次加减(如A := A + SETPOINT * 0.01)或三角函数(SIN,COS)计算得到,误差会被放大。此时容差需放宽。经验公式:
$$ \text{tolerance} \geq \text{原始误差} \times \text{运算步数} \times 1.5 $$
例如原始 AD 采样误差0.0005,经 20 步累加,容差建议 ≥0.0005 × 20 × 1.5 = 0.015。 -
遵守控制周期与响应要求
在高速控制(如伺服位置环,周期 1 ms)中,过大的容差会导致调节滞后。此时应结合控制算法特性调整:- P 控制器:容差可略宽(如
0.01) - PID 控制器(含积分项):需更严(如
0.001),避免积分饱和后“卡死”在容差带内
- P 控制器:容差可略宽(如
常用容差参考值(适用于多数中低速过程控制):
| 物理量类型 | 典型量程 | 推荐容差 | 说明 |
|---|---|---|---|
| 温度(PT100) | 0–200 ℃ | 0.1 |
传感器分辨率通常为 0.1 ℃ |
| 压力(4–20 mA) | 0–10 bar | 0.02 |
对应电流分辨率 0.002 mA |
| 流量(脉冲计数) | 0–1000 L/min | 0.5 |
脉冲当量常为 0.1–1 L/脉冲 |
| 位置(编码器) | 0–1000 mm | 0.05 |
17 位编码器分辨率达 0.0076 mm,但机械间隙更大 |
ST 代码实战:封装为可复用函数块
重复写 ABS(A - B) < TOL 易出错且难维护。应将其封装为函数块(Function Block),实现一次定义、多处调用。
步骤 1:创建函数块 FB_RealEqual
在 PLC 编程软件(如 TwinCAT、Codesys、Unity Pro)中新建函数块,接口定义如下:
FUNCTION_BLOCK FB_RealEqual
VAR_INPUT
A : REAL; // 待比较值 A
B : REAL; // 待比较值 B
Tolerance : REAL := 0.001; // 容差,默认 0.001
END_VAR
VAR_OUTPUT
Equal : BOOL; // TRUE 表示在容差内相等
END_VAR
步骤 2:编写主体逻辑(ST 语言)
// 计算绝对差值
Equal := ABS(A - B) < Tolerance;
✅ 优势:调用时只需
EqualFlag := FB_RealEqual(A := MyTemp, B := SetTemp);,无需重复写ABS和<;修改容差只需改默认值或传参。
步骤 3:增强健壮性(处理特殊值)
IEEE 754 规定浮点数包含 NaN(非数字)、±INF(无穷大)。若 A 或 B 为 NaN,ABS(A - B) 结果仍为 NaN,而 NaN < X 永远为 FALSE,导致 Equal 错误地返回 FALSE。应主动检测:
// 增强版逻辑(含 NaN 检测)
IF IS_NAN(A) OR IS_NAN(B) THEN
Equal := FALSE; // NaN 不等于任何值,包括自身
ELSIF IS_INF(A) OR IS_INF(B) THEN
Equal := (A = B); // 仅当同为 +INF 或同为 -INF 时才相等
ELSE
Equal := ABS(A - B) < Tolerance;
END_IF;
⚠️ 注意:
IS_NAN()和IS_INF()是 IEC 61131-3 标准函数,但部分老旧 PLC 固件可能不支持。若不可用,可用变通法检测NaN:
IF A <> A THEN // NaN 的唯一特征:不等于自身
常见错误模式与修正对照表
下表列出工程师在浮点比较中高频踩坑的写法,并给出正确替代方案:
| 错误写法 | 问题分析 | 正确写法 | 说明 |
|---|---|---|---|
IF A = B THEN |
逐位比较,忽略浮点误差 | IF ABS(A - B) < 0.001 THEN |
必须用容差 |
IF A - B = 0.0 THEN |
同上,且 A - B 可能因舍入产生新误差 |
IF ABS(A - B) < 0.001 THEN |
差值本身也是浮点数,仍需容差 |
IF ROUND(A*1000) = ROUND(B*1000) THEN |
强制转整数,但 ROUND() 函数在 PLC 中可能不支持或有平台差异;且放大后溢出风险高 |
IF ABS(A - B) < 0.001 THEN |
直接比较差值最通用、最安全 |
IF A >= B - 0.001 AND A <= B + 0.001 THEN |
逻辑等价但冗长,易漏写括号或写反不等号 | IF ABS(A - B) < 0.001 THEN |
ABS 写法简洁、不易错 |
IF A = B THEN ... ELSIF A > B THEN ... |
第一分支必失败,导致逻辑永远走 ELSIF 分支 |
改用 IF ABS(A - B) < TOL THEN ... ELSIF A > B + TOL THEN ... |
“大于”分支也需加容差,否则 A=1.0005, B=1.0 时 A>B 为 TRUE,但若 A=1.0002,A>B 可能为 FALSE(因浮点误差),造成边界模糊 |
高级技巧:动态容差与自适应比较
在精密控制场景(如半导体温控、张力闭环),固定容差可能不足。可采用动态策略:
方法 1:相对容差(Relative Tolerance)
当 A 和 B 数值跨度大时(如从 0.001 到 1000),用绝对容差 0.001 对小值足够,但对大值过严。此时改用相对误差:
// 相对容差判断:|A−B| / max(|A|,|B|) < ε
IF A = 0.0 AND B = 0.0 THEN
Equal := TRUE;
ELSIF A = 0.0 OR B = 0.0 THEN
Equal := ABS(A - B) < 0.001; // 至少一个为零,退回绝对容差
ELSE
Equal := ABS(A - B) < (0.001 * MAX(ABS(A), ABS(B)));
END_IF;
方法 2:滑动窗口均值滤波后比较
对噪声大的模拟量(如老式热电偶信号),先做 5 点滑动平均再比较,可抑制随机波动干扰:
// 假设 AvgTemp 是已更新的 5 点均值
IF ABS(AvgTemp - SetTemp) < 0.05 THEN
Heater_ON := FALSE;
END_IF;
最后检查清单:部署前必做 5 步验证
- 确认所有
REAL比较均已替换:全局搜索= REAL或= R#,确保无遗漏。 - 核对容差值与物理量纲一致:检查单位(℃?bar?mm?),避免
0.001被误用在温度比较中(应为0.1)。 - 测试边界值:手动赋值
A := 100.0005; B := 100.0000;,验证ABS(A-B)<0.001返回TRUE;再试A := 100.0015,应返回FALSE。 - 注入 NaN 测试:用调试工具强制将某变量设为
NaN,确认函数块返回FALSE而非异常。 - 观察运行日志:在首次投运 24 小时内,记录比较结果为
TRUE的频次。若每秒触发数百次,说明容差过大;若一周不触发,说明过小或逻辑有误。

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