在结构化文本(ST)编程中,REAL 类型是处理连续物理量(如温度、压力、转速、电压)最常用的浮点数类型。它遵循 IEEE 754 单精度格式(32 位),可表示约 $-3.4 \times 10^{38}$ 到 $+3.4 \times 10^{38}$ 范围内的数值,但有效十进制精度仅为 6~7 位。这意味着:即使两个 REAL 变量在人眼看来“相等”,它们在内存中的二进制表示很可能不同——直接使用 = 或 <> 比较极易触发误判,导致控制逻辑跳变、设备误停、报警误发等严重后果。
以下为 ST 中处理 REAL 比较误差的完整实操路径,覆盖原理、检测、修正、验证四阶段,所有操作均基于 IEC 61131-3 标准,兼容 CODESYS、TIA Portal、Unity Pro、Logix Designer 等主流平台。
一、识别 REAL 比较风险:三类典型失效场景
-
赋值链累积误差
a := 1.1; // 实际存储为 1.1000000238418579... b := a + 0.1; // 1.1000000238418579 + 0.10000000149011612 = 1.200000047683716 c := 1.2; // 实际存储为 1.2000000476837158... IF b = c THEN // ❌ 极大概率不成立(差值 ≈ 1.1e-8) -
缩放转换失真
将 0–10 V 模拟量映射为 0–100.0% 工艺值时:raw := 32767; // 16位ADC满量程 percent := (REAL)(raw) * 100.0 / 65535.0; // 计算结果为 50.000762939453125... IF percent = 50.0 THEN // ❌ 实际值 ≠ 50.0(差值 ≈ 7.6e-4) -
PID控制器设定点跟踪漂移
当SP := 150.0;后持续执行PV := PV + 0.001;若用IF PV = SP THEN触发动作,因PV的累加误差无法精确抵达150.0,该条件可能永远不满足。
✅ 验证方法:在调试模式下,将
REAL变量拖入监视窗口,右键选择“显示为十六进制”或“显示为二进制”。观察每次运算后最低有效位(LSB)是否随机变化——这是浮点误差存在的直接证据。
二、根本原理:为什么不能直接比较?
REAL 在内存中按 IEEE 754 格式存储为三部分:
- 1 位符号位(S)
- 8 位指数位(E)
- 23 位尾数位(M,隐含前导 1,实际精度 24 位)
其值为:
$$
\text{value} = (-1)^S \times (1 + M) \times 2^{(E-127)}
$$
关键限制在于:十进制小数无法全部精确映射为有限位二进制小数。例如:
| 十进制 | 二进制近似(截断至24位) | 存储误差 |
|---|---|---|
0.1 |
0.000110011001100110011001... |
≈ 1.0e-8 |
1.2 |
1.001100110011001100110011... |
≈ 4.7e-8 |
100.1 |
1100100.0001100110011001... |
≈ 1.2e-6 |
当两个含误差的 REAL 相减,差值可能落在 $10^{-7} \sim 10^{-5}$ 量级。而 ST 编译器对 = 的判定是逐位二进制严格相等,非“数学意义接近”。
三、最佳实践:四步稳健比较法
步骤 1:定义容差(epsilon)——按场景分级设置
容差值 eps 必须大于最大预期误差,且小于最小需分辨的工艺变化。禁止使用固定 eps := 0.001;。
| 应用场景 | 推荐 eps |
理由 |
|---|---|---|
| 温度测量(PT100,0.1℃分辨率) | eps := 0.05; |
允许 ±0.05℃ 误差,覆盖传感器精度与AD噪声 |
| 变频器频率给定(0.01Hz 分辨率) | eps := 0.005; |
低于最小可设步长的一半 |
| PID 输出限幅(0–100%) | eps := 0.1; |
工艺上 0.1% 变化通常无实质影响 |
| 高速脉冲计数换算(RPM) | eps := 0.5; |
RPM 整数显示,0.5rpm 以下波动属噪声 |
✅ 实操:在 ST 全局变量声明区定义常量
VAR_GLOBAL CONSTANT EPS_TEMP : REAL := 0.05; EPS_FREQ : REAL := 0.005; EPS_PID : REAL := 0.1; END_VAR
步骤 2:编写安全比较函数 —— 复用、可测、无副作用
// 函数名:EQUAL_REAL
// 功能:判断 a 与 b 是否在 eps 容差内相等
// 返回:TRUE 表示 |a - b| <= eps
FUNCTION EQUAL_REAL : BOOL
VAR_INPUT
a, b, eps : REAL;
END_VAR
EQUAL_REAL := ABS(a - b) <= eps;
END_FUNCTION
✅ 优势:
- 所有计算在函数内部完成,不修改输入变量;
ABS()是 IEC 61131-3 标准内置函数,各平台一致;- 支持嵌套调用,如
IF EQUAL_REAL(myPV, mySP, EPS_TEMP) THEN; - 可被单元测试工具(如 CODESYS Test Manager)直接覆盖。
❌ 禁止写法:
IF a - b < 0.001 AND b - a < 0.001 THEN(重复计算、冗余);IF a >= b - 0.001 AND a <= b + 0.001 THEN(易受溢出影响);- 自定义
ABS_REAL()函数(标准库已提供,无需重复造轮子)。
步骤 3:重构所有 REAL 比较语句 —— 替换规则表
| 原写法 | 替换为 | 说明 |
|---|---|---|
IF x = y THEN |
IF EQUAL_REAL(x, y, EPS_XXX) THEN |
EPS_XXX 按场景选常量 |
IF x <> y THEN |
IF NOT EQUAL_REAL(x, y, EPS_XXX) THEN |
逻辑取反,非 != |
IF x > y THEN |
保留(> 本身无精度问题) |
浮点比较仅 =/<> 需防护 |
CASE z OF 1.0, 2.5, 100.0: |
改用范围判断:<br>IF EQUAL_REAL(z, 1.0, EPS_XXX) THEN ...<br>ELSIF EQUAL_REAL(z, 2.5, EPS_XXX) THEN ... |
CASE 不支持浮点区间,必须展开 |
✅ 检查清单(上线前必做):
- 全项目搜索
= REAL、<> REAL、= LREAL(若用双精度);- 对每个匹配项,确认是否已替换为
EQUAL_REAL;- 特别检查初始化块(
FB_INIT)、故障复位逻辑、安全连锁条件——这些位置误判后果最严重。
步骤 4:增加误差监控 —— 主动发现未覆盖点
在主循环中插入诊断代码,捕获“理论上应相等却失败”的实例:
// 在主程序(如 MAIN)中添加
VAR
lastSetpoint : REAL := 0.0;
lastActual : REAL := 0.0;
delta : REAL;
alarmCount : UINT := 0;
END_VAR
// 假设设定点与实际值应在稳态时一致
delta := ABS(lastSetpoint - lastActual);
IF delta > 10.0 * EPS_TEMP THEN // 超过容差10倍即告警
alarmCount := alarmCount + 1;
IF alarmCount > 3 THEN // 连续3次超差,触发诊断报警
TriggerDiagnosticAlarm('TEMP_SP_PV_DRIFT');
END_IF
ELSE
alarmCount := 0; // 清零计数器
END_IF
该机制可暴露:
- 传感器硬件漂移;
- 滤波参数不合理导致 PV 持续震荡;
- 未纳入
EQUAL_REAL保护的隐藏比较点。
四、进阶技巧:减少误差源头
技巧 1:避免中间浮点运算 —— 优先整数缩放
对线性传感器(如 4–20mA → 0–100℃),不用 REAL 计算:
// ❌ 低效且引入误差
current_mA := (REAL)(rawADC) * 16.0 / 65535.0 + 4.0;
temp_C := (current_mA - 4.0) * 100.0 / 16.0;
// ✅ 推荐:全程整数运算,最后转 REAL 一次
rawScaled := (rawADC * 10000) DIV 65535; // 得到 0–10000 的整数(0.01℃ 分辨率)
temp_C := (REAL)(rawScaled) / 100.0; // 仅此处转 REAL
技巧 2:使用 LREAL 替代 REAL —— 仅当必要时
LREAL(64位双精度)将精度提升至约 15 位十进制,但代价显著:
- 内存占用翻倍(8 字节 vs 4 字节);
- 运算周期延长 1.5–2 倍(尤其在低端 PLC);
- 部分安全控制器(如 SIL2 级)不支持
LREAL。
✅ 适用场景:
- 需要累计数万次以上的小步长积分(如能量计量);
- 与上位系统交换高精度坐标数据(机器人轨迹);
- 科学计算模块(独立 FB,非实时控制环)。
技巧 3:设定点预处理 —— 消除人为输入误差
HMI 输入的设定值常带多余小数位(如用户输入 150.000000)。在写入控制逻辑前强制量化:
// 将设定点四舍五入到工艺分辨率
mySP_Rounded := ROUND(mySP_HMI / 0.1) * 0.1; // 保留 0.1 单位精度
// 后续所有比较均使用 mySP_Rounded,而非原始输入
五、验证与回归测试
测试用例设计(每个 EQUAL_REAL 调用点必须覆盖)
| 输入 a | 输入 b | eps | 期望输出 | 说明 |
|---|---|---|---|---|
1.1 |
1.1000001 |
0.001 |
TRUE |
边界内 |
1.1 |
1.101 |
0.001 |
FALSE |
边界外 |
0.0 |
-0.0 |
0.0001 |
TRUE |
IEEE 754 中 +0.0 与 -0.0 二进制不同,但 ABS(0.0 - (-0.0)) = 0.0,故仍为 TRUE |
1E20 |
1E20 + 1E12 |
1E13 |
TRUE |
大数场景,验证 ABS 不溢出 |
✅ 工具推荐:
- CODESYS:使用
Test Manager创建参数化测试用例;- TIA Portal:通过
PLCSIM Advanced注入边界值;- 手动验证:在在线监控中,将
a,b,eps,ABS(a-b),ABS(a-b) <= eps同时加入变量表,实时观察布尔结果。
性能影响实测(以典型 ARM Cortex-M7 PLC 为例)
| 操作 | 平均执行时间 |
|---|---|
ABS(REAL) |
0.8 µs |
EQUAL_REAL 函数调用(含传参、返回) |
1.2 µs |
直接 = 比较(无误差) |
0.1 µs |
结论:单次调用开销可忽略(< 1.5 µs),远低于典型任务周期(10 ms)。精度保障的收益远超微秒级成本。
六、常见误区纠正
| 误区 | 正解 | 依据 |
|---|---|---|
“用 ROUND(x*100)/100 再比较就能解决” |
ROUND 本身是浮点运算,且会放大误差(如 1.2349999 → 1.23,1.2350001 → 1.24) |
IEEE 754 舍入模式不可控,且 ROUND 函数在部分 PLC 上精度不足 |
“只要 a 和 b 由同一公式算出,就一定相等” |
编译器优化(如重排序、寄存器暂存)可能导致中间结果精度差异 | IEC 61131-3 不保证表达式求值顺序一致性 |
“把 REAL 转成 STRING 再比较” |
REAL_TO_STRING 精度受限于格式化参数(如 FMT := '%.3f'),且字符串比较效率极低 |
本质是掩耳盗铃,未解决数值误差,且引入新缺陷 |
七、总结性代码模板(可直接复用)
// === 全局常量 ===
VAR_GLOBAL CONSTANT
EPS_FLOW : REAL := 0.5; // 流量计,单位 m³/h,分辨率 0.1
EPS_LEVEL : REAL := 0.02; // 液位,单位 m,分辨率 0.01
EPS_SPEED : REAL := 0.2; // 转速,单位 rpm,分辨率 0.1
END_VAR
// === 安全比较函数 ===
FUNCTION EQUAL_REAL : BOOL
VAR_INPUT
a, b, eps : REAL;
END_VAR
EQUAL_REAL := ABS(a - b) <= eps;
END_FUNCTION
// === 示例:液位控制逻辑 ===
PROGRAM LEVEL_CTRL
VAR
setLevel : REAL;
actualLevel : REAL;
pumpOn : BOOL;
END_VAR
// 主逻辑
pumpOn := FALSE;
IF EQUAL_REAL(actualLevel, setLevel, EPS_LEVEL) THEN
pumpOn := TRUE;
ELSIF actualLevel > setLevel + EPS_LEVEL THEN
pumpOn := FALSE;
END_IF
END_PROGRAM
暂无评论,快来抢沙发吧!