C# 补码

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

如:

某款仪器说明书:

某款仪器通讯说明书:

那对于文档中所述:

寄存器数据 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,就能得到对应的有符号负数。

总结

这段话的核心是:

  1. 补码表示:32位整数中,最高位为1表示负数

  2. 转换方法:可以通过直接类型转换,或者用公式计算

  3. -1的表示0xFFFFFFFF 在补码中表示 -1

在实际编程中,直接使用 (int)rawValue 是最简单正确的做法,因为C#的int类型就是使用补码表示的

在计算机当中都是使用补码来进行计算和存储的,因为晶体管这些元器件没有正和负的概念, 使用补码后,加法和减法可以统一用一套加法器电路来完成。

个人浅见,望各位道友不吝赐教。

相关推荐
£漫步 云端彡3 分钟前
Golang学习历程【第十一篇 接口(interface)】
开发语言·学习·golang
virus59458 小时前
悟空CRM mybatis-3.5.3-mapper.dtd错误解决方案
java·开发语言·mybatis
一匹电信狗8 小时前
【LeetCode_547_990】并查集的应用——省份数量 + 等式方程的可满足性
c++·算法·leetcode·职场和发展·stl
初次见面我叫泰隆8 小时前
Qt——3、常用控件
开发语言·qt·客户端
鱼跃鹰飞9 小时前
Leetcode会员尊享100题:270.最接近的二叉树值
数据结构·算法·leetcode
无小道9 小时前
Qt——QWidget
开发语言·qt
时艰.9 小时前
Java 并发编程之 CAS 与 Atomic 原子操作类
java·开发语言
梵刹古音10 小时前
【C语言】 函数基础与定义
c语言·开发语言·算法
筵陌10 小时前
算法:模拟
算法
梵刹古音10 小时前
【C语言】 结构化编程与选择结构
c语言·开发语言·嵌入式