C# 從補碼中獲取有符號數的實際數值
原理
計算機存儲數據時,默認是存儲數據的補碼。有符號的數粗存在符號位(最高位)。
這裏就會提到原碼、反碼、補碼的概念。
原碼:用符號位和數值表示帶符號數,正數的符號位用“0”表示,負數的符號位用“1”表示,數值部分用二進制形式表示。
反碼:正數的反碼與原碼相同,負數的反碼爲對該數的原碼除符號位外各位取反。
補碼:正數的補碼與原碼相同,負數的補碼爲對該數的原碼除符號位外各位取反,然後在最後一位加1.
思路
下位機上傳有符號數時,直接上傳的是補碼。上位機解析時,需要手動轉換:
一、當數爲負數時。
0.獲取該數據的補碼,去掉最高位(符號位)並將其合併到一個結構體中(數據通信一般以字節爲單位)。
1.空位(不用的位)補全,高位不用的置1。
2.將補碼-1
3.然後再取反
4.乘上比例尺與-1
二、當數爲正數時。
0.獲取該數據的補碼,去掉最高位(符號位)並將其合併到一個結構體中(數據通信一般以字節爲單位)。
1.乘上比例尺
示例
下位機上傳的數據中有8個字節,其中第4(從0開始)字節的低四位和第5字節的高7位組成一個參數,這個參數是有符號的數據,與整數之間的比例尺是0.1。要求解析出該參數。
代碼
// 獲取數據
byte[] data = new byte[8];
data = GetDataFromUsb();
// 解析數據
// 當上傳的數據爲負數時
if ((UInt16)(data[4] & 0x08) == 0x08)
{
// 去掉最高位獲取數據
UInt16 s = (UInt16)(((data[4] & 0x07) << 7) + (UInt16)((data[5] >> 1) & 0x7F));
// 空位補全,高位不用的置1
UInt16 s1 = (UInt16)(s | 0xFC00);
// 補碼-1
UInt16 s2 = (UInt16)(s1 - 1);
// 取反
UInt16 s3 = (UInt16)~s2;
// 乘上-1和比例尺
_Speed = -(s3 * 0.1f);
}
// 當待解析的數據爲正數時
else
{
// 去掉最高位獲取數據
UInt16 s = (UInt16)(((data[4] & 0x07) << 7) + (UInt16)((data[5] >> 1) & 0x7F));
// 乘上比例尺
_Speed = s * 0.1f;
}
圖表解釋
1._Speed參數的佔位情況。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
---|---|---|---|---|---|---|---|---|
data[4] | F_Speed | _Speed | _Speed | _Speed | ||||
data[5] | _Speed | _Speed | _Speed | _Speed | _Speed | _Speed | _Speed |
其中data[4]的第3位爲我們符號位,也就是F_Speed。當我們做有符號碼轉換時,可以直接忽略,因爲他不影響我們真實值的改變。
2.空位補全緣由
當我們使用更大的空間來做容器填充數據時,負數的空位補全。
UInt16也就等於2個字節
第0步運算之後,我們數據如下圖所示。
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
_S | _S | _S | _S | _S | _S | _S | _S | _S | _S |
我們知道,上面的所有_S的數據都是元數據的補碼,因爲該數是負數,所以我們需要將這個數據的符號位置爲1,其它未使用的位(14、13、12、11、10)也置爲1。這樣我們就可以通過-1再求反碼得到負數的值了。
積跬步以至千里:) (:一陣沒來由的風