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

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

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

相关推荐
噜啦噜啦嘞好37 分钟前
生产者消费者模型
linux·开发语言
CoovallyAIHub40 分钟前
2025年值得关注的5款数据标注工具
深度学习·算法·计算机视觉
Blossom.11841 分钟前
基于Qwen2-VL+LayoutLMv3的智能文档理解系统:从OCR到结构化知识图谱的落地实践
开发语言·人工智能·python·深度学习·机器学习·ocr·知识图谱
稚辉君.MCA_P8_Java42 分钟前
Gemini永久会员 VB返回最长有效子串长度
数据结构·后端·算法
小年糕是糕手43 分钟前
【C++】类和对象(五) -- 类型转换、static成员
开发语言·c++·程序人生·考研·算法·visual studio·改行学it
星释43 分钟前
Rust 练习册 106:太空年龄计算器与宏的魔法
开发语言·后端·rust
Xの哲學44 分钟前
Linux内核数据结构:设计哲学与实现机制
linux·服务器·算法·架构·边缘计算
diegoXie44 分钟前
PCRE Lookaround (零宽断言)总结(R & Python 通用)
开发语言·python·r语言
任子菲阳1 小时前
学Java第五十二天——IO流(下)
java·开发语言·intellij-idea