有符號數和無符號數的轉換及思考

2021-09-25 18:34:49 字數 3764 閱讀 6585

1 有符號數和無符號數的表示

三者的最高位均為符號位.

我以前一直沒弄明白的是為何8位補碼的表示範圍是-128~127, 今天查閱了相關資料,於此記下。

仍然以8位為例:

原碼

原碼的表示範圍-127~-0, +0~+127, 共256個數字。

正0的原碼是0000 0000, 負0的原碼是1000 0000, 有正0負0之分, 不符合人的習慣, 待解決.

反碼

除符號位, 原碼其餘位取反而得

+0:0000 0000,-0:1111 1111 仍然有正0負0之分。

補碼

在反碼的基礎上加1而得

對原碼的兩種0同時末位加1

+0:0000 0000,-0:0000 0000(因為溢位導致8位全0)

消除了正0負0之別, 如此一來, 便節省出乙個數值表示方式1000 0000, 不能浪費, 用來表示-128,-128特殊之處在於沒有相應的反碼原碼。也可以這樣考慮:

-1: 1111 1111

-2: 1111 1110(在-1的基礎上減1,直接將補碼減1即可)

-3: 1111 1101(在-2補碼基礎上減1,以下類似)

-4: 1111 1100

……-127:1000 0001

-128:1000 000012

3456

7如此以來:8位補碼表示範圍是-128~+127因為0只有一種形式所以,仍然是256個數

若8位代表無符號數, 則表示範圍是 : 0~255, 這就是為什麼高階語言講到資料型別,

比如c++中的short型別時(16位長)說其表示範圍是:-32768~+32767,而unsigned short表示的範圍則是:0~65535

2 有符號和無符號數的表示

在計算機中無符號數用原碼表示, 有符號數用補碼表示

w位補碼表示的值為:

−xw−12w−1+∑w−2i=1xi2i

(xi為補碼的第i位, i從0開始)

最高位 也稱符號位,1表示負數,0表示正數,符號位為0時,和無符號數的表示是相同的,以下是4位補碼的示例:

0101=−0∗23+1∗22+0∗21+1∗20=5

1101=−1∗23+1∗22+0∗21+1∗20=−3

w位的補碼表示的數值範圍是[-2w-1, 2w-1-1]

如4位的補碼表示的最小值是-8(1000), 最大值是7(0111).

只有理解了有符號數的補碼表示, 才能真正理解無符號數和有符號數的轉換、有符號數的截斷和溢位等問題.

3 陷阱

在c語言中,如果乙個運算包含乙個有符號數和乙個無符號數,那麼c語言會隱式地將有符號數轉換為無符號數,這對於標準的算術運算沒什麼問題,但是對於 < 和 > 這樣的關係運算子來說,它會出現非直觀的結果,這種非直觀的特性經常會導致程式中難以察覺的錯誤

看下面的例子:

int strlonger(char *s, char *t)12

34上面的函式看起來似乎沒什麼問題, 實際上當s比t短時,函式的返回值也是1, 為什麼會出現這種情況呢?

原來strlen的返回值型別為size_t,c語言中將size_t定義為unsigned int,當s比t短時,strlen(s) - strlen(t)為負數,但無符號數的運算結果隱式轉換為無符號數就變成了很大的無符號數.

為了讓函式正確工作,**應該修改如下 :

return strlen(s) > strlen(t);

2023年, 從事freebsd開源作業系統專案的程式設計師意識到,他們對getpeername函式的實現存在安全漏洞.**的簡化版本如下:

//void *memcpy(void *dest, void *src, size_t n);

#define ksize 1024

char kbuf[ksize];

int copy_from_kernel(void *user_dest, int maxlen)12

3456

78910

1112

你看出了問題所在嗎?

4 擴充套件、截斷和溢位

4.1 轉換

當資料型別轉換時,同時需要在不同資料大小,以及無符號和有符號之間轉換時,c語言標準要求先進行資料大小的轉換,之後再進行無符號和有符號之間的轉換.

c語言中的強制型別轉換保持二進位制位值不變,只是改變解釋位的方式

看以下**:

short int v = -12345;

unsigned short uv = (unsigned short)v;

printf("v = %d, uv = %u\n」, u, uv);12

3輸出如下:

v = -12345, uv = 53191

由於-12345的16位補碼表示與53191的16位無符號表示是完全一樣的,所以會得到以上輸出。

無符號數和有符號數之間的轉換是一一對應的關係,w位的有符號數s轉換無符號數u的對應關係為:

如4位有符號數7(0111)轉換為無符號數也是7,而4位有符號數-1(1111)轉換為無符號數是15。

類似地,w位的無符號數u轉換為有符號數s的對應關係為:

如4位無符號數5(0101)轉換為無符號數也是5,而4位無符號數13(1101)轉換為無符號數為-3。

其實只要知道無符號數和有符號數對二進位制位的解釋方式,無需記住上述的對應關係,也能算出轉換後的值。

4.2 擴充套件

將無符號數轉換為更大的資料型別時, 只需簡單地在開頭新增0,這種運算稱為0擴充套件

將有符號數轉換為更大的資料型別需要執行符號擴充套件,規則是將符號位擴充套件至所需要的位數

同樣對於如下的例子

// 如果是char, 那麼系統認為最高位是符號位, 而int可能是16或者32位, 那麼會對最高位進行擴充套件

signed char c7_1 = 0xff;

// 如果是unsigned char, 那麼不會擴充套件.

unsigned char c7_2 = 0xff;

// 最高位若為0時, 二者沒有區別, 若為1時,則有區別

printf(「signed %08x, %d\n」, c7_1, c7_1);

printf(「unsigned %08x, %d\n」, c7_2, c7_2);12

3456

7signed 0xffffffff, -1

unsigned 0xff, 255

如果是unsigned char, 那麼不會擴充套件, 0xff仍然成為0x000000ff, 值為255

最高位若為0時, 二者沒有區別, 若為1時,則有區別

示例程式

#include

#include

///int main(void)12

3456

78910

1112

1314

1516

1718

1920

2122

2324

2526

2728

2930

3132

33如將4位的二進位制數1001(-7)擴充套件為8位的結果為11111001(-7).

4.3 截斷

將乙個大的資料型別轉換為小的資料型別時,不管是無符號數還是有符號數都是簡單地進行位截斷

無符號數的數值大小可能因截斷而變化,而有符號數不僅數值大小可能變化,符號位也可能發生改變,如8位二進位制數00011001(25)轉換為4位數截斷的結果是1001(-7).

4.4 溢位

有符號數和無符號數

有符號和無符號整數 1.通常情況下,大多數字預設的是有符號數,比如 4,5 要想寫乙個無符號數必須在後面加u 比如 4u,5u 2.在計算機中有符號數是用補碼的形式來表示的,最高位是符號位。無符號數就是正數唄 正數的補碼和原碼相同。比如 1 在計算機中表示為 11111111 11111111 11...

無符號數和有符號數

人有十個手指頭,習慣了逢十進一,於是十進位製成了生活中的標準。程式的世界只有高低電平兩種狀態,更適合用二進位制來表示,於是二進位製成了程式世界的標準。對與無符號數來說,我們更喜歡談他們之間的轉化,十進位制是我們最習慣的進製,於是十進位制轉為r進製,r進製轉為十進位制變尤為重要。十進位制 r進製 整數...

有符號數和無符號數

刷題刷到了有符號數的問題,回顧一下知識點 有符號數 signed 可以區分正負數 最高位表示數值的正負 0表示正數,1表示負數 無符號數 unsigned 不區分正負數 只有整數型別 雖然無符號數所能表示的最大數值大於有符號數,但兩者所能表示的數值範圍相同。eg.short int 128,0 0,...