搞PLC编程这些年,我有个特别深的体会:数据类型的坑,往往比逻辑错误更难发现。尤其是在三菱PLC的ST语言里,INT和LREAL这种看似基础的选择,真能让人在设备调试现场熬几个通宵。今天要聊的核心问题就是你看到的标题:三菱ST选型,INT和LREAL到底哪个会回绕(wrap around)?
先说结论,省得你着急:INT会回绕,LREAL不会回绕。但事情远没有这么简单。LREAL虽然不回绕,却有精度丢失和溢出变无穷的毛病,任何一个处理不当都能让设备运行结果莫名其妙地错。这篇文章会从回绕的底层机制讲起,结合三菱ST的实际编程场景,把手动选型的判断依据、测试方法、常见坑和责任都讲清楚,顺便把我调试中遇到过的几个典型故障翻出来复盘一遍。
这篇文章适合正在用三菱GX Works2/GX Works3写ST程序的人,也适合现场设备维护、准备做程序优化的工程师。哪怕你是刚接触结构化文本的新手,看完也能明白为什么计数器选INT会突然变成负数,以及什么时候该狠下心用LREAL。
1. 先搞明白"回绕"到底是什么意思
很多人在选型时把"会不会回绕"挂在嘴边,但真问一句"回绕是什么",又说不清楚。这个现象最早让我想起的是老式汽车里程表:99999公里再往前开,表盘会跳回00000。这叫绕回来了,数值没有按线性方向继续增长,而是从最高位重新回到最低位。
1.1 从里程表到二进制的溢出
PLC里的整数变量存储是有边界的。INT是三菱ST里最常用的16位有符号整数,它的最大值是32767。当你在ST程序里执行nValue := nValue + 1,而 nValue 已经是32767时,内存里的二进制位全部变成1,再加1就会进位到符号位上。从人的角度看,数值一下子从32767跳到了-32768。
这个现象的专业术语叫"溢出回绕"。它不是说程序报错,也不是数据丢失,而是数值存储的位宽不够,高位被硬生生截断。放在PLC里最可怕的一点是:CPU根本不会给你任何提示,程序继续跑,逻辑继续执行,但数据已经错得离谱了。
用ST代码直观演示一下:
VAR nValue : INT; END_VAR nValue := 32767; nValue := nValue + 1; (* 结果:-32768 *) nValue := nValue + 1; (* 结果:-32767 *)我在GX Works3里监视这段代码时,第一次看到这种跳变也愣了一下。从32767变到-32768,这中间没有任何过渡,就是一瞬间的事。如果你的程序用这个值去做比较判断,比如IF nValue > 0 THEN,那结果会完全反转,设备动作也跟着乱套。
1.2 LREAL为什么不会回绕
LREAL是三菱ST中的64位双精度浮点数,范围大约可以到±1.7976931348623157E+308。这个范围大得离谱,在工业现场的应用尺度下,靠累加永远摸不着它的边。所以要让它像INT那样回绕到负数,基本不存在这种可能性。
但浮点数有自己的麻烦。LREAL的精度大约只有15到17位有效数字,超过这个有效位数后,整数部分的低位就开始不精确了。也就是说,LREAL虽然不会从正数绕回负数,但它可能在你不注意的时候"偷懒",加了一个很小的数却等于没加。这个我们后面专门写一节,因为它也是隐蔽性极强的问题源。
从机制上说,我的理解是:回绕是定点整数位宽不够的必然行为,而浮点数靠指数和尾数的科学计数法结构,把"回绕"问题转化成了"精度丢失"和"溢出到无穷"的问题。两者都很危险,只是危险呈现的方式不一样。
2. INT与LREAL的本质差异
在三菱ST的选型里,INT和LREAL经常是同一个变量位两种极端。短期测试看不出问题,长期运行就暴露。我把这两个类型的关键差异列成了一张对照表,方便你贴在工位上:
| 特性 | INT | LREAL |
|---|---|---|
| 位宽 | 16位 | 64位 |
| 数值范围 | -32768 到 32767 | 约±1.797E308 |
| 精度 | 精确到整数1 | 约15~17位有效数字 |
| 溢出行为 | 回绕到负值 | 溢出到±无穷 |
| 内存占用 | 2字节 | 8字节 |
| 运算速度 | 快 | 相对慢 |
| 典型用途 | 计数、索引、小范围循环 | 模拟量换算、PID、位置计算 |
注意看"溢出行为"这一行:INT是从正跳到负,LREAL是变成无穷大。无穷大在ST里显示为类似"Inf"的内容,无论你怎么比较它都会出问题。所以在三菱PLC里,两个类型都有可能让程序出状况,只不过一个表现成"突然变负数",一个表现成"值成了不可理喻的巨数"。
2.1 三菱ST里还有哪些候选类型
选型不要只盯INT和LREAL。三菱ST语言里整数类型至少还有DINT(32位有符号整型)和LINT(64位有符号整型),浮点类型还有REAL(32位单精度浮点)。DINT的取值范围从约-21亿到21亿,比INT大了六万多倍。我最常说的一句话是:如果担心回绕,第一反应应该是换DINT,而不是直接跳到浮点。
再提一下REAL。有些三菱CPU型号并不支持LREAL,只有REAL。REAL是32位浮点,范围约±3.4E38,精度约7位有效数字。做一般的模拟量比例运算足够,但你要是拿它去累加微小增量值,运行个把月后误差可能大到让你怀疑程序被改过。
这里给出一个判断原则:能用整数表达的计数场景,一律不上浮点;必须做小数运算的场景,REAL/LREAL按CPU支持情况选。浮点看似"通用",但在PLC这种追求确定性的环境里,不该用的时候用它就是在给未来埋雷。
2.2 为什么有人非要用LREAL不可
LREAL在ST里最舒服的使用场景是工程单位换算。比如模拟量通道读进来是0到16000的原始值,你要把它转换成0到100度温度,中间有比例、偏置、滤波,甚至可能要做二阶运算。这种场景你用INT做,每一步都要想着舍入问题,换算来换算去最后可能差出好几度;用LREAL做,中间过程完全可以放开手,最后输出前再转成整数给硬件通道就行。
但选LREAL的人也要清楚一件事:并不是运算包含小数就非LREAL不可。比如整数除以整数后只要保留一位小数,你用REAL也完全可以。LREAL真正不可替代的地方,在于数值跨度极大、运算级联极多、对精度要求又高的场合。如果只是日常温控PID,REAL往往已经绰绰有余了。
3. 实测:两种类型的回绕行为
光讲理论没意思。我在调试三菱程序时习惯写一段小测试代码,在监视表里盯着数值看。这种直观观察比翻手册管用得多。下面这段ST代码是专门测INT回绕行为用的,你可以直接抄到自己的工程里去试:
3.1 用测试程序看INT怎么回绕
VAR nTestInt : INT; nMaple : DINT; END_VAR // 测试INT、DINT的累加回绕 nTestInt := 32767; nTestInt := nTestInt + 1; // 监视 nTestInt:结果是-32768 nMaple := 2147483647; nMaple := nMaple + 1; // 监视 nMaple:结果是-2147483648在GX Works3的监视表中,你可以给nTestInt添加数值变化记录,或者干脆连续执行多个扫描周期看趋势。我实测的结果是:INT从32767变成-32768,DINT从2147483647变成-2147483648。规律完全一致,只是翻车的时间点不同。
如果你用同样的方法测LREAL,你会发现它怎么累加都不会回绕到负数。我在程序里试过连续把LREAL变量加到10的15次方级别,数值依然单调递增。但紧接着就遇到了新问题,见下一节。
实测结论已经很清楚了:回绕问题只发生在整型,而且一定发生在达到该类型上限的那一刻。你又想知道"哪个会回绕",答案就是INT你会回绕,LREAL不会回绕。但请注意:这三菱的ST指令里,如果表达式两边出现类型不一致,往往会产生隐式转换。这个转换过程也是很多"看似不该出问题"的故障源头。
3.2 浮点溢出不是回绕,但同样危险
LREAL不回绕,不代表它不会出问题。当LREAL数值增长超过约1.797E308时,它会变成带符号的无穷大。在三菱ST里,这个值参与任何运算都不正常:比较指令可能会一直满足条件,或者计算结果全变成NaN(非数字)。
我在某台设备上遇到过温度显示瞬间变成"Inf"的情况。排查到最后,原因是程序里有一次除法运算,分母在一个极端的运行状态下变成了0。浮点除法除以0在三菱ST中不会像整数除法那样直接触发硬件中断,而是产生一个无穷大结果,然后这个无穷大在后续所有温度平均计算里传播,整个温度数组全部沦陷。最坑的是,不仔细看数据流根本找不到源头。
所以给准备用LREAL的人一个忠告:所有浮点除法都要在运算前判断分母范围。就算你没有除法的需求,也要对输出的LREAL值做合理性限幅,防止异常数据污染下游逻辑。
3.3 精度丢失的隐蔽陷阱
LREAL的第二个陷阱就是开头提到的精度问题。双精度浮点能精确表示的最大整数是2的53次方,也就是9007199254740992,约9千万亿。超过这个数,再加一就没反应了。虽然一般工业计数到不了这个量级,但有一种情况完全可能,就是把很小的小数额增量反复累加。
举个例子。你用一个LREAL变量累积某种流量的积分值,每次扫描周期加上0.0001,PLC扫描周期10毫秒,一分钟就加6000次,一小时就是36万次。运行几个小时后,累加值到了几千,这时候浮点的相对精度仍然够用。但如果这段程序连续运行几十天,累加值到几十万上百万后,浮点的有效数字位数就不够精确表示每次的微小增量的变化了,最终结果会与真实值产生不可忽视的偏差。
用整数做累加就不会有这种问题,但整数要面临溢出回绕,是个两难的抉择。我的习惯是:这类累计积分值用DINT保存累加次数,再用一个LREAL值保存最后的结果,两者配合。运行时间再长,也只是从DINT转成LREAL的那一刻损失一点舍入误差。
4. 实际场景中的选型决策
讲了这么多理论,最终要落到现场选型。我总结了一些工程场景下的选型习惯,你可以直接拿来当参考。这些经验是我在多个项目调试中反复踩坑后才形成的,没有教科书会说这些。
4.1 计数、脉冲、位置累加:别用INT
凡是涉及累加计数的场景,INT基本都不适合。别觉得"这个设备每天只生产几千个产品,INT够用了",INT上限是32767,听起来好像很宽裕,但生产线上运行几个班次就很容易突破。
我参与过的一个包装项目,产品计数器一开始用的是三菱ST里的INT变量。操作工每生产完一个产品,计数器加1。按每分钟60个算,9小时多就会从32767回绕到-32768。结果就是早班结束时产量还是正的,夜班一开始显示屏上突然冒出负几万个。操作工当时以为是PLC出故障了,一查程序才发现是数据类型选错了。
这类场景我现在的标准做法是:计数需求哪怕再小,也用DINT。DINT的回绕点约21亿,对绝大多数产线来说相当于"永生"。如果连DINT都不放心,可以选择LINT(64位整型),但三菱多数CPU用于计数器时DINT已经完全够用。
编码器位置的累加更是如此。伺服电机编码器一圈脉冲少则几千,多则几十万,你用一个INT去累加位置脉冲,连一圈都存不满。位置控制必须在DINT或LREAL级别工作,这没有任何争议。具体的定位指令内部如果已经用了32位寄存器,你用户程序里最好也用DINT去承接,免得中间转换截断出幺蛾子。
4.2 模拟量、PID、工程单位换算:用LREAL
模拟量通道的原始值通常是一个整数,比如0到16000、0到4095。但你在程序里要做的往往是比例缩放、零点迁移、滤波平均,这些操作一上整数就难受。我的习惯是把原始值读进来先转成REAL或LREAL,再在浮点域中完成所有处理,最后输出前转回INT。
PID运算更是典型。三菱有些自带PID指令的CPU可以直接传变量地址,但用ST手写PID逻辑时,所有比例、积分、微分系数都应该用浮点类型。三菱ST里LREAL做这种运算,有效数字长,不容易在中间过程中损失精度。REAL虽然也够用,但如果你CPU型号支持LREAL,用起来也不会有什么额外的性能压力。
这里有一个容易被忽视的细节:从模拟量模块读出来的整数,转换成LREAL时不要先转成INT再做运算。直接写LREAL(rawValue)或者用三菱提供的转换指令,一次转到位。所有的ST转换函数我后面列一张表给你。
4.3 数组索引、循环变量:INT够用但要防溢出
数组下标和循环计数器这类场景,INT是非常合适的选择。三菱ST中数组元素访问速度很快,用整数做索引不会有浮点那种精度和边界问题。比如FOR i := 0 TO 99 DO ... END_FOR,这个 i 声明为INT完全合理。
但如果循环次数可能超过32767,就要小心了。我调试过一台设备,数组长度本身只有几百,但在一个嵌套循环里,某个变量被当成中间计数器反复累计,最后意外超过了32767。程序单步执行时看不出问题,因为正常流程到不了那么大,可一旦循环条件被某个外部参数改掉,立即引爆。
所以我现在的习惯是:对任何外部参数可控的循环,我都用DINT做索引变量,只把固定的短循环用INT。反正内存多占两个字,换来的是彻底的安心。
4.4 类型转换的正确打开方式
在ST程序里,INT和LREAL不会自动混用。你不能直接写LREAL_Var := INT_Var,中间必须经过类型转换指令。三菱ST中有一批标准的转换函数,我看到过不少人记错或者干脆省略,导致编译阶段报类型错误:
// 正确写法 INT_TO_REAL(nIntValue, dRealValue); // 16位整数转32位浮点 DINT_TO_LREAL(dDintValue, lrValue); // 32位整数转64位浮点 // 三菱ST也允许函数式写法(取决于CPU和支持库) lrValue := DINT_TO_LREAL(dDintValue);转换时机也很关键。我建议尽量在数据进入运算域的第一时间完成转换,不要在循环里反复做类型切换。每次转换看似没什么成本,但PLC扫描周期里成百上千次转换叠加起来,对快速响应程序的影响还是有的。更重要的是,转换过程中可能有舍入和截断,反复来回转会让误差越搞越大。
5. 常见问题与排查笔记
很多工程师是在设备运行异常时才想起数据类型的事。这一部分我把实际项目维修记录里的典型问题整理一下,方便你遇到类似症状时快速对照。
5.1 变量突然变成负值
故障现象:设备运行一段时间后,某个计数值或累加位置值突然变成负数。排查方向:先看变量类型是不是INT。如果确定是,再看是否已经达到32767附近。我遇到过的情况里,九成是INT回绕,还有一成是负数逻辑本身导致。
处理办法:把那个变量改成DINT,然后在赋值处做一次类型检查。如果程序里有其他逻辑依赖原来的位宽,改动后要注意所有引用该变量的指令是否还能正常运行。批量替换前,先在软件里搜索一下所有引用点。
5.2 累加值到一定大小后不动了
故障现象:LREAL累加变量运行几天后基本停在某个值附近不再变化。排查方向:检查这个LREAL值有没有超过2的53次方。没超过的话,检查每次累加增量是否太小,小于当前值可表示的最小步长。例如上百万的数值,增量只有0.001,浮点数完全可能"吃掉"这个增量。
处理办法:把累加逻辑改成分层累加:用DINT记录累加次数,用LREAL记录结果,计算时再相乘。或者加大每次扫描周期的累加增量,把精度需求调到当前数值量级能表达的范围。这么做以后,我还没碰到过再次"停住不动"的情况。
5.3 编译报错与类型不匹配
故障现象:ST程序编译时报"数据类型不一致"或类似提示。最常见的原因就是没有做类型转换。比如你把一个REAL直接赋值给LREAL,或者把一个INT直接和LREAL比较,三菱ST不是完全拒绝,而是根据具体指令和CPU版本有不同表现。最稳妥的做法是把所有比较和赋值的两侧类型显式转换。
处理办法:养成写代码的习惯,所有跨类型操作都用转换函数。我自己的代码里,看到某个赋值语句两侧类型不一致,就算编译能过,我也会先改掉。这种"靠编译器兜底"的习惯早晚会坑你一次。
5.4 程序扫描造成的隐形重复累加
故障现象:计数不准确,数值跳变没有规律,时大时小。排查方向:用INT还是LREAL只是问题的一部分,还要看这个累加指令是不是在每个扫描周期都被执行了多次。比如三菱ST里有些代码块在多个POU中被调用,同一个扫描周期内执行了两次。
处理办法:给累加操作加一个执行条件,或者使用脉冲执行指令。这里和数据类型无关,但选型过程中如果不排查清楚,你会误以为是INT回绕或LREAL精度问题,白白浪费几个小时。先确认执行频率,再怀疑回绕。
5.5 CPU不支持LREAL怎么办
不是所有三菱CPU都支持LREAL,特别是小型一体化CPU。我在一个改造项目里就碰上过:程序在GX Works3里能编译,但下载到设备CPU后报错,查手册才发现该机型ST语言只支持REAL。这种情况只能用REAL,并把代码里所有LREAL改成REAL。
REAL和LREAL在用法上几乎一样,但精度范围差了一个量级。改用REAL后,要把所有累加类逻辑重新审视一遍,必要时转用DINT加量值转换。这个经验对我来说很深刻:写代码之前先看CPU手册的数据类型支持列表,省得后面返工。
6. 一些选型上的经验体会
最后说点我自己的体会。三菱ST里INT和LREAL的选择,核心其实就一句话:回绕是整型的标签,精度是浮点的软肋。INT会回绕,而且回绕得悄无声息;LREAL不回绕,但会精度失真甚至溢出到无穷。真正优秀的程序不追求某个类型"万能",而是让每个变量都待在自己该待的位置上。
我现在的处理原则很固定:计数、脉冲、索引这一类"数量"问题,一律DINT起步,绝不碰INT;模拟量、比例运算、PID这一类"量值"问题,优先用LREAL或REAL;所有跨类型运算,一律显式转换;所有浮点除法,写之前先处理分母为0的情况。
这套习惯帮我避免了很多现场半夜被叫起来改程序的尴尬。选型这种事,难不是难在理解定义,而是难在没有养成肌肉记忆。建议你把本文里的测试代码复制到自己的工程里跑一遍,亲手在监视表里看到32767变成-32768,比读十遍理论都来得深刻。下次再有人问你三菱ST选型INT和LREAL哪个会回绕,你可以直接把监视表截图甩他脸上。