C语言有符号3字节数据转换成4字节数据

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

前言(不想看细节请直接移步 ->总结)

最近做的项目是关于心电ECG和呼吸阻抗的数据采集,用到的是TI的传感器ADS1292R。而心电和呼吸阻抗数据是分别以由两个有符号24位(3个字节)的形式传回单片机,但是上位机程序的变量类型都是2字节或者4字节的(需要先转换成4字节的数据类型才能参与运算),上位机也没有由3字节直接变成4字节的有符号数据类型的转换函数,采用直接强制转换的话又会导致数据错误。

针对此问题,本文的提出了一个基于C语言实现3字节有符号整数向4字节有符号整数转换的方法,这个方法简单高效,经过实测在Dev c++编译器和单片机上应用都没有问题。

一、原码反码补码知识回顾

我们知道,数据在程序里都是以补码的形式存储的,这样对于减法运算也能看成加上其负数的加法,极大地减少复杂电路设计的开销。有关原码反码补码知识可参考这个链接:C语言——原码, 反码, 补码 详解
正数的补码、反码、补码都是其本身;
负数的反码是在其原码的基础上, 符号位不变,其余各个位取反;负数的补码是在反码的基础上再+1。

而对于3字节的ECG数据来说,其也是以补码的形式传回来的,其最高位(第24位)是其符号位。例如,对于+1的24位的原码反码补码表达都为0x000001;而-1的24位表达则是——原码:0x800001;反码:0x ff ff fe;补码:0x ff ff ff。

也就是说,若单片机接收到3字节的数据为0x ff ff ff,则代表数据的取值为-1,通过DAC原理便可反推出ECG信号的电压值了。

二、有符号3字节数据强制转成4字节数据会有什么错误?

对于24位的正数(0 ~ 2^23-1),采用直接强制转换(在前面拼接上一个字节数据0x00)的方式,转换成四字节的数据,结果是没问题的,因为正数的原码、反码、补码都相同;

#include<stdio.h>
int main()
{ 
	unsigned char x2,x1,x0; // x2、x1、x0分别对应24位数的23~16、15~8、7~0位
	int y;         // y是转换后的4字节有符号数据
	// 对于24位数+1的表示方法:
	// 原码0x 00 00 01
	// 反码0x 00 00 01
	// 补码0x 00 00 01
	x2 = 0x00;
	x1 = 0x00;
	x0 = 0x01;
	y  = 0x00<<24 | x2<<16 | x1<<8 | x0;
	printf("y=%d\n", y);  // 输出:y=1 ;与实际情况相符
	return 0;
}  

但是对于24位的负数(-2^23 ~ 0),采用直接强制转换(在前面拼接上一个字节数据0x00)的方式,转换成四字节的数据,结果就有问题了:

#include<stdio.h>
int main()
{ 
	unsigned char x2,x1,x0; // x2、x1、x0分别对应24位数的23~16、15~8、7~0位
	int y;         // y是转换后的4字节有符号数据
	// 对于24位数-1的表示方法:
	// 原码0x 80 00 01
	// 反码0x ff ff fe
	// 补码0x ff ff ff
	x2 = 0xff;
	x1 = 0xff;
	x0 = 0xff;
	y  = 0x00<<24 | x2<<16 | x1<<8 | x0;
	printf("y=%d\n", y);  // 输出:y=16777215 ;与实际情况不符
	return 0;
}  

这也很容易想得通,因为从三字节扩充到四字节的有符号数据时,三字节数据的符号位会被四字节数据吞并为数据位,这直接关系到数据的正负取值性质。

三、将24位数据扩充为32位数据的方法(不改变数值和符号)

那么怎么样完成数据的正负继承,且不会改变数值呢?
做法一:我们在转换上面的负数时,若把拼接在前面的一个字节数据0x00换成0xff,转换成四字节的负数数据就又对得上了(这跟负数补码的生成有关):

#include<stdio.h>
int main()
{ 
	unsigned char x2,x1,x0; // x2、x1、x0分别对应24位数的23~16、15~8、7~0位
	int y;         // y是转换后的4字节有符号数据
	// 对于24位数-1的表示方法:
	// 原码0x 80 00 01
	// 反码0x ff ff fe
	// 补码0x ff ff ff
	x2 = 0xff;
	x1 = 0xff;
	x0 = 0xff;
	y  = 0xff<<24 | x2<<16 | x1<<8 | x0;
	printf("y=%d\n", y);  // 输出:y=-1 ;与实际情况相符!
	return 0;
}  

这说明,可以通过判断24位最高位的符号位来选择在前面拼接0x00还是0xff,以实现3字节数据格式向4字节的数据格式转换。
当然,这是比较笨的做法。为什么说这种做法笨呢?看看下面这个方法你就会不禁感叹原来可以更加巧妙地实现:

#include<stdio.h>
int main()
{ 
	unsigned char x2,x1,x0; // x2、x1、x0分别对应24位数的23~16、15~8、7~0位
	int y;         // y是转换后的4字节有符号数据
	// 对于24位数-1的表示方法:
	// 原码0x 80 00 01
	// 反码0x ff ff fe
	// 补码0x ff ff ff
	x2 = 0xff;
	x1 = 0xff;
	x0 = 0xff;
	y  = (char)x2<<16 | x1<<8 | x0;
	printf("y=%d\n", y);  // 输出:y=-1 ;与实际情况相符!
	return 0;
}  

为什么呢,我们来看看进行(char)强制转换后各个变量的十六进制打印:

#include<stdio.h>
int main()
{ 
	unsigned char x2,x1,x0; // x2、x1、x0分别对应24位数的23~16、15~8、7~0位
	int y=0;         // y是转换后的4字节有符号数据
	// 对于24位数-1的表示方法:
	// 原码0x 80 00 01
	// 反码0x ff ff fe
	// 补码0x ff ff ff
	x2 = 0xff;
	x1 = 0xff;
	x0 = 0xff;
	y  = (char)x2<<16 | x1<<8 | x0;
	printf("y = %8x , %d\n", y, y);                        // 输出:y = ffffffff , -1 与实际情况相符
	printf("(char)x2<<16 = %8x , %d\n", (char)x2<<16, (char)x2<<16);  // 输出:(char)x2<<16 = ffff0000 , -65536
	printf("(char)x1<<8 = %8x , %d\n", (char)x1<<8, (char)x1<<8);    // 输出:(char)x1<<8 = ffffff00 , -256
	printf("(char)x0 = %x , %d\n", (char)x0, (char)x0);		  // 输出:(char)x0 = ffffffff , -1
	printf("x2<<16 = %8x , %d\n", x2<<16, x2<<16);			  // 输出:x2<<16 = ff0000 , 16711680
	printf("x1<<8 = %8x , %d\n", x1<<8, x1<<8);				  // 输出:x1<<8 = ff00 , 65280
	printf("x0 = %8x , %d\n", x0, x0);					  // 输出:x0 = ff , 255
	
	return 0;
 } 

可以看到0xff经过(char)强制转换后,不左移其16进制竟然拓展到了32位(为了确认不是打印格式的问题,我还特意把中间那行%8x的8去掉),取值为0xffffffff ! 而左移8位则变成0xffffff00,左移16位变到0xffff0000;
没有进行强制转换的无符号数据左移虽也会拓展,但是其前面并只会补0;而进行(char)x2强制转换的输出y的高八位会跟x2的最高位(符号位)始终保持相同!
再举几个例子,收到-65536与+65536的补码:

#include<stdio.h>

int main()
{ 
	unsigned char x2,x1,x0; // x2、x1、x0分别对应24位数的23~16、15~8、7~0位
	int y=0;         // y是转换后的4字节有符号数据
	// 对于24位数-65536的表示方法:
	// 原码0x 81 00 00
	// 反码0x fe ff ff
	// 补码0x ff 00 00
	x2 = 0xff;
	x1 = 0x00;
	x0 = 0x00;
	y  = (char)x2<<16 | x1<<8 | x0;
	printf("y = %8x , %d\n", y, y);                        // 输出:y = ffff0000 , -65536 与实际情况相符
	printf("(char)x2<<16 = %8x , %d\n", (char)x2<<16, (char)x2<<16);  // 输出:(char)x2<<16 = ffff0000 , -65536
	printf("(char)x1<<8 = %8x , %d\n", (char)x1<<8, (char)x1<<8);    // 输出:(char)x1<<8 = 0 , 0
	printf("(char)x0 = %8x , %d\n", (char)x0, (char)x0);		  // 输出:(char)x0 = 0 , 0
	printf("x2<<16 = %8x , %d\n", x2<<16, x2<<16);			  // 输出:x2<<16 = ff0000 , 16711680
	printf("x1<<8 = %8x , %d\n", x1<<8, x1<<8);				  // 输出:x1<<8 = 0 , 0
	printf("x0 = %8x , %d\n", x0, x0);					  // 输出:x0 = 0 , 0
			// 对于24位数+65536的表示方法:
	// 原码0x 01 00 00
	// 反码0x 01 00 00
	// 补码0x 01 00 00
	x2 = 0x01;
	x1 = 0x00;
	x0 = 0x00;
	y  = (char)x2<<16 | x1<<8 | x0;
	printf("y = %8x , %d\n", y, y);                        // 输出:y = 10000 , 65536 与实际情况相符
	printf("(char)x2<<16 = %8x , %d\n", (char)x2<<16, (char)x2<<16);  // 输出:(char)x2<<16 = 10000 , 65536
	printf("(char)x1<<8 = %8x , %d\n", (char)x1<<8, (char)x1<<8);    // 输出:(char)x1<<8 = 0 , 0
	printf("(char)x0 = %8x , %d\n", (char)x0, (char)x0);		  // 输出:(char)x0 = 0 , 0
	printf("x2<<16 = %8x , %d\n", x2<<16, x2<<16);			  // 输出:x2<<16 = 10000 , 65536
	printf("x1<<8 = %8x , %d\n", x1<<8, x1<<8);				  // 输出:x1<<8 = 0 , 0
	printf("x0 = %8x , %d\n", x0, x0);					  // 输出:x0 = 0 , 0
	return 0;
	return 0;
 } 

总结

  • 正数的补码、反码、补码都是其本身;负数的反码是在其原码的基础上, 符号位不变,其余各个位取反,负数的补码则是在反码的基础上再+1。

  • 在程序存储以及数据传输中,数据常以补码的形式出现。

  • 3字节有符号数据采用拼接的方式强制转换变成4字节数据时,若想保存数值与符号都不变,不能直接在高八位加上0x00或者0xff;需要根据三字节的符号位(第24位)进行判断,为0则拼接上0x00,为1则拼上0xff。

  • 可以将三字节数据的最高字节数据进行有符号的强制类型转换(char),强制转换后会拓展出一个四字节的操作数,且比符号位高的高位数据会自动跟符号位取值一样,在这个基础上再与低字节相拼接,这样做就能简单巧妙地,在不改变24位数据正负和取值的条件下完成32位数据的扩展。

  • 简洁有效代码段:y = (char)x2<<16 | x1<<8 | x0; // 三字节拼接扩展成四字节

    #include<stdio.h>
    int main()
    { 
    	unsigned char x2,x1,x0; // x2、x1、x0分别对应24位数的23~16、15~8、7~0位
    	int y;         // y是转换后的4字节有符号数据
    	// 对于24位数-1的表示方法:
    	// 原码0x 80 00 01
    	// 反码0x ff ff fe
    	// 补码0x ff ff ff
    	x2 = 0xff;
    	x1 = 0xff;
    	x0 = 0xff;
    	y  = (char)x2<<16 | x1<<8 | x0;  // 三字节拼接扩展成四字节
    	printf("y=%d\n", y);  // 输出:y=-1 ;与实际情况相符!
    	return 0;
    }  
    
    原文作者:胡思_不乱想
    原文地址: https://blog.csdn.net/weixin_43532704/article/details/125338434
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞