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类型就是使用补码表示的

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

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

相关推荐
AI软著研究员3 小时前
程序员必看:软著不是“面子工程”,是代码的“法律保险”
算法
FunnySaltyFish4 小时前
什么?Compose 把 GapBuffer 换成了 LinkBuffer?
算法·kotlin·android jetpack
Ray Liang4 小时前
用六边形架构与整洁架构对比是伪命题?
java·python·c#·架构设计
颜酱4 小时前
理解二叉树最近公共祖先(LCA):从基础到变种解析
javascript·后端·算法
地平线开发者20 小时前
SparseDrive 模型导出与性能优化实战
算法·自动驾驶
董董灿是个攻城狮21 小时前
大模型连载2:初步认识 tokenizer 的过程
算法
地平线开发者21 小时前
地平线 VP 接口工程实践(一):hbVPRoiResize 接口功能、使用约束与典型问题总结
算法·自动驾驶
罗西的思考21 小时前
AI Agent框架探秘:拆解 OpenHands(10)--- Runtime
人工智能·算法·机器学习
HXhlx1 天前
CART决策树基本原理
算法·机器学习
Wect1 天前
LeetCode 210. 课程表 II 题解:Kahn算法+DFS 双解法精讲
前端·算法·typescript