Modbus协议4个byte转成单精度float类型数据原理解析和基于Java的两种方式实现
Posted Steven Jon
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Modbus协议4个byte转成单精度float类型数据原理解析和基于Java的两种方式实现相关的知识,希望对你有一定的参考价值。
前言
学习过Modbus协议的同学应该知道对于4x地址(保持寄存器)里面存的数据都是整数,那对于浮点数,Modbus协议是如何处理的呢?
举栗子:
浮点数26.3存储方式如下:
为什么要这样存储浮点数呢?
因为IEEE754标准(IEEE二进制浮点数算术标准)定义了两种浮点数格式:32位单精度和64位双精度,如下图所示:
IEEE 754 的规定:
-
32位单精度格式中包含1bit符号位s,8bit阶码e,23bit尾数f
-
64位双精度格式中包含1bit符号位s,11bit阶码e,52bit尾数f
一个浮点数 (Value) 可以这样表示:
Value = sign * exponent * fraction
也就是浮点数的实际值,等于符号位(sign bit)乘以指数偏移值(exponent bias)再乘以分数值(fraction)。
原理分析
就拿上面的例子,浮点数26.3,用了两个寄存器保存,数值分别是16850和26214,转成16进制分别是0x41D2和0x6666,那浮点数26.3可以用0x41D26666表示,将0x41D26666代入格式: SEEE EEEE EMMM MMMM MMMM MMMM MMMM MMMM
整个转化过程如下:
0x41D26666 转二进制01000001110100100110011001100110
S E E E E E E E E M M M M M M M M M M M M M M M M M M M M M M M
0 1 0 0 0 0 0 1 1 1 0 1 0 0 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0
逐步分解:
1#符号位
S
0
S = 0 0为正,1为负
2#阶码:
E E E E E E E E
1 0 0 0 0 0 1 1
EEEEEEEE = 10000011 = 131
指数值e = (EEEEEEEE) - 127 = 4
3#尾数
M M M M M M M M M M M M M M M M M M M M M M M
1 0 1 0 0 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0
4#在尾数的左边有一个省略的小数点和1,这个1在浮点数的保存中经常省略,
加上一个1和小数点到尾数的开头,得到尾数值如下:
1.1 0 1 0 0 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0
5#根据指数值e调整尾数:一个负的指数值向左移动小数点,一个正的指数值向右移动小数点
指数值e = 4,因此小数点向右移动4位,得到尾数值如下:
1 1 0 1 0 . 0 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0
6#小数点左边代表整数,右边代表小数
整数:1 1 0 1 0
小数:0 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0
7#整数部分换算
整数:1 1 0 1 0
1 x 2^4 + 1 x 2^3 + 0 x 2^2 + 1 x 2^1 + 0 x 2^0
简化上面过程:
2^4 + 2^3 + 2^1 = 26
8#小数部分换算
小数:0 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0
0 x 2^-1 + 1 x 2^-2 + 0 x 2^-3 + 0 x 2^-4 + 1 x 2^-5 + 1 x 2^-6 + 0 x 2^-7 + 0 x 2^-8 + 1 x 2^-9 + 1 x 2^-10 + 0 x 2^-11 + 0 x 2^-12 + 1 x 2^-13 + 1 x 2^-14 ....
简化上面:
2^-2 + 2^-5 + 2^-6 + 2^-9 + 2^-10 + 2^-13 + 2^-14 + 2^-17 + 2^-18 = 0.2999992370605469 = 0.3
9#浮点数值 = 整数 + 小数 = 26 + 0.3 = 26.3 符号位=0,不用 x (-1)
将上面整个过程整合成这个公式:
fValue = (-1) ^ S x 2 ^ (E - 127) x (1 + M)
参数说明:
fValue:非0浮点数数值
S:数符,0正1负
E: 阶码8位,以2为底,阶码 = 阶码真值 + 127
M:尾数23位,采用隐含尾数最高位为1的表示方法,实际尾数24位,尾数真值 = 1 + 尾数
Java代码实现
- 将上面的转换过程写成代码:
public static float toFloat(short s1, short s2)
//将输入数值short转化为无符号unsigned short
int us1 = s1, us2 = s2;
if (s1 < 0) us1 += 65536;
if (s2 < 0) us2 += 65536;
//sign: 符号位, exponent: 阶码, mantissa:尾数
int sign, exponent;
float mantissa;
//计算符号位
sign = us1 / 32768;
//去掉符号位
int emCode = us1 % 32768;
//计算阶码
exponent = emCode / 128;
//计算尾数
mantissa = (float)(emCode % 128 * 65536 + us2) / 8388608;
//代入公式 fValue = (-1) ^ S x 2 ^ (E - 127) x (1 + M)
return (float)Math.pow(-1, sign) * (float)Math.pow(2, exponent - 127) * (1 + mantissa);
测试结果:
> Task :Test.main()
请输入第一个值:
16850
请输入第二个值:
26214
转换结果:26.3
请输入第一个值:
-15918
请输入第二个值:
26214
转换结果:-26.3
- 最简单方式,使用
ByteArrayInputStream
和DataInputStream
:
public static float toFloat(byte[] bytes) throws IOException
ByteArrayInputStream mByteArrayInputStream = new ByteArrayInputStream(bytes);
DataInputStream mDataInputStream = new DataInputStream(mByteArrayInputStream);
try
return mDataInputStream.readFloat();
finally
mDataInputStream.close();
mByteArrayInputStream.close();
测试结果:
try
byte[] vs = new byte[]0x41, (byte)0xd2, (byte)0x66, (byte)0x66;
System.out.println(toFloat(vs));
byte[] vs2 = new byte[](byte)0xC1, (byte)0xd2, (byte)0x66, (byte)0x66;
System.out.println(toFloat(vs2));
catch (IOException e)
e.printStackTrace();
> Task :Test.main()
26.3
-26.3
这两种实现方式可根据需求自行调用。
非常感谢你能看到这里,如果能够帮助到你是我的荣幸!
以上是关于Modbus协议4个byte转成单精度float类型数据原理解析和基于Java的两种方式实现的主要内容,如果未能解决你的问题,请参考以下文章