在工控行业中,使用的补码的地方目前就遇到过一种情况:与仪器仪表通讯时,如果数据是负数的话,一般会用补码表示。
如:
某款仪器说明书:

某款仪器通讯说明书:

那对于文档中所述:
寄存器数据 00 00 00 84转十进制为132,若数据为FF FF FE C9则为负数,FF FF FE C9-FF FF FF FF=(4294966985-4294967295)-1=-311。FF FF FF FF表示-1
这段话是什么意思呢?
这段话是在解释 32位有符号整数补码 的转换方法。
1. 数据背景
这是在处理 32位整数 ,使用 4个字节(8个十六进制数字) 表示:
-
00 00 00 84= 正数 132 -
FF FF FE C9= 负数 -311
2. 补码原理
在32位有符号整数中:
-
最大值 :
0x7FFFFFFF= 2,147,483,647 -
最小值 :
0x80000000= -2,147,483,648 -
-1的表示 :
0xFFFFFFFF
3. 原文的转换过程
第1步:将十六进制转十进制原始值
FF FF FE C9(十六进制)
= 4294966985(十进制无符号)
第2步:补码转换公式
文中的计算:
FF FF FE C9 - FF FF FF FF = (4294966985 - 4294967295) - 1 = -311
实际上更标准的算法是:
cs
// 如果值 >= 0x80000000,说明是负数
if (rawValue >= 0x80000000)
{
// 补码转原码:取反加1,然后取负号
//C#中只要你使用了~,编译器都会给你先将无符号整型变为有符号整型,再进行按位取反操作
return -(int)(~rawValue + 1);
}
为什么值 >= 0x80000000是负数:
32位整数的范围是:
有符号(int):-2,147,483,648 到 2,147,483,647
无符号(uint):0 到 4,294,967,295
0x80000000 的二进制是1000 0000 0000 0000 0000 0000 0000 0000
第一位是符号位,为1则代表负数
正数范围:
0x00000000 = 0
0x00000001 = 1
0x00000002 = 2
...
0x7FFFFFFF = 2,147,483,647(最大正数)
负数范围(补码表示):
0x80000000 = -2,147,483,648(最小负数)
0x80000001 = -2,147,483,647
...
0xFFFFFFFF = -1
4. 计算方法
cs
// 验证 FF FF FE C9 = -311
uint rawValue = 0xFFFFFEC9; // 4294966985
uint max = 0xFFFFFFFF;
// 方法1:补码公式
int signed1 = (int)rawValue; // -311
// 方法2:取反加一
uint inverted = ~rawValue; // 取反:0x00000136 = 310
int signed2 = -(int)(inverted + 1); // -(310 + 1) = -311
// 方法3:用最大值计算
int signed3 = (int)rawValue - (int)max -1; // 4294966985 - 4294967295 -1 = -311
6. 实际工程应用
cs
public class Modbus32BitConverter
{
/// <summary>
/// 将4个字节的Modbus寄存器转换为32位有符号整数
/// </summary>
/// <param name="highRegister">高位寄存器</param>
/// <param name="lowRegister">低位寄存器</param>
/// <returns>32位有符号整数</returns>
public int ConvertRegistersToInt32(ushort highRegister, ushort lowRegister)
{
// Modbus通常是高位在前
uint combined = ((uint)highRegister << 16) | lowRegister;
// 直接转换为有符号int(自动处理补码)
return (int)combined;
}
/// <summary>
/// 测试示例
/// </summary>
public void TestConversions()
{
// 示例1:00 00 00 84 = 132
ushort high1 = 0x0000; // 00 00
ushort low1 = 0x0084; // 00 84
int result1 = ConvertRegistersToInt32(high1, low1);
Console.WriteLine($"00 00 00 84 → {result1}"); // 输出:132
// 示例2:FF FF FE C9 = -311
ushort high2 = 0xFFFF; // FF FF
ushort low2 = 0xFEC9; // FE C9
int result2 = ConvertRegistersToInt32(high2, low2);
Console.WriteLine($"FF FF FE C9 → {result2}"); // 输出:-311
// 示例3:FF FF FF FF = -1
ushort high3 = 0xFFFF;
ushort low3 = 0xFFFF;
int result3 = ConvertRegistersToInt32(high3, low3);
Console.WriteLine($"FF FF FF FF → {result3}"); // 输出:-1
}
}
7. 文中公式解释
文中公式 FF FF FE C9 - FF FF FF FF = (4294966985 - 4294967295) - 1 = -311 可以这样理解:
0xFFFFFEC9 = 4294966985
0xFFFFFFFF = 4294967295
= 4294966985 - 4294967295 - 1
= -310 - 1
= -311
这实际上是一个简化算法:用无符号值减去最大值再减1,就能得到对应的有符号负数。
总结
这段话的核心是:
-
补码表示:32位整数中,最高位为1表示负数
-
转换方法:可以通过直接类型转换,或者用公式计算
-
-1的表示 :
0xFFFFFFFF在补码中表示 -1
在实际编程中,直接使用 (int)rawValue 是最简单正确的做法,因为C#的int类型就是使用补码表示的
在计算机当中都是使用补码来进行计算和存储的,因为晶体管这些元器件没有正和负的概念, 使用补码后,加法和减法可以统一用一套加法器电路来完成。
个人浅见,望各位道友不吝赐教。