日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

两个负数相减计算机如何表示,计算机如何表示整数

發布時間:2025/3/20 编程问答 21 豆豆
生活随笔 收集整理的這篇文章主要介紹了 两个负数相减计算机如何表示,计算机如何表示整数 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

[TOC]

在計算機中,任何的數據都是用二進制: 0 和 1 來表示。整數也不例外。生活中的 10,在 8 個字節的整數中表示為 00001010。但是這樣子只能表示正數和零。怎么表示負數呢?于是有了符號位的概念。在 8 個字節的整數中,最高位為符號位,0 代表正數,1 代表負數。所以 -10 就可以用 10001010 來表示。但是,直接采用符號位會帶來一系列問題:00000000 和 10000000 表示為 0 和 -0 ,那么用那個來表示 0,還是都是零?

加法問題。

關于加法問題,試著運算 1 - 1 的值:將 1 - 1 轉換成 1 + (-1)

轉換成二進制:00000001 + 10000001

運算結果為 10000010 轉換成十進制為 -2

很明顯,這個結果不對。為了解決這個問題,在計算機中引入補碼(2's complement)來解決。要解釋為什么要使用補碼,還得從無符號整型開始說起。為了便于理解和方便,我用 3 個字節的整數來講解。但由于 C 語言最小都是 8 個字節,在代碼驗證方面使用 8 個字節。

無符號整型(unsigned integer)

3 個字節的無符號整型中,可以表示 2^3^ = 8 個數:000 = 2^2 * 0 + 2^1 * 0 + 2^0 * 0 = 0+0+0 = 0

001 = 2^2 * 0 + 2^1 * 0 + 2^0 * 1 = 0+0+1 = 1

010 = 2^2 * 0 + 2^1 * 1 + 2^0 * 0 = 0+2+0 = 2

011 = 2^2 * 0 + 2^1 * 1 + 2^0 * 1 = 0+2+1 = 3

100 = 2^2 * 1 + 2^1 * 0 + 2^0 * 0 = 4+0+0 = 4

101 = 2^2 * 1 + 2^1 * 0 + 2^0 * 1 = 4+0+1 = 5

110 = 2^2 * 1 + 2^1 * 1 + 2^0 * 0 = 4+2+0 = 6

111 = 2^2 * 1 + 2^1 * 1 + 2^0 * 1 = 4+2+1 = 7

既然是整數,免不了要加減乘除。

在計算機中,只用加法可以完成的整數的四則運算:減法,就是加上一個負數。

乘法,就是不斷的做加法。

除法,就是不斷的做減法,而減法又可以轉換成加法。

但是這樣會出現問題:兩個無符號整數相加,超出了 3 個字節表示的范圍怎么辦?比如:3 + 7。

兩個無符號整數相減。轉換成加上一個負數。既然是無符號整數,哪里來的負數?

要想解決這個問題,抬頭看看墻上的鐘吧。

時鐘系統

假設現在是 4 點鐘:

但實際上現在已經六點鐘了,你現在要把指針調到 6 點去。你可以這么做:順時針調整 2 個小時:

逆時針調整 10 個小時:

順時針調整 14 個小時,逆時針調整 22 個小時…

把上述過程用數學來表示:順時針旋轉幾小時等于加上幾個小時。逆時針則為減去幾個小時。有:4 + 2 = 6

4 - 10 = 6

4 + 14 = 6

4 - 22 = 6

時鐘的一圈是 12 個小時。也就是說,在時鐘系統中,向上溢出和向下溢出,都通過模 12 來解決問題

$$

4 - 10 \equiv 4 + (-10) \equiv 4 + (-10 \bmod 12) \equiv 4 + 2 \equiv 6 \pmod{12}

\\

$$上面可以說成,4 與 -10 同余

負數怎么取余

整數取余我們都了解,一直把被余數減去余數直到小于 0 即可。同理,負數取余我們可以把被余數加上余數知道大于 0 即可。

根據上面思路,很快我們可以寫出代碼:/**

* number 被余數

* mod 余數

*/

int min_mod(int number, int mod)

{

if (number >= 0) {

while (number - mod >= 0) {

number = number - mod;

}

return number;

}

else {

while (number + mod < 0) {

number = number + mod;

}

return number + mod;

}

}

上面雖然好理解,但是代碼執行效率不高,時間復雜度為 O(n)。

我們將余數(remainder)加入除法中,有:

$$

\frac{divident}{divisor} = quotient \dots remainder

$$

轉換一下:

$$

divident = quotient \times divisor + remainder \qquad 1

$$

交換一下順序,可以得出余數為:

$$

remainder = divident - quotient \times divisor \qquad 2

$$

其中商(quotient)是被除數(divident)與除數(divisor)相除向下取整(floor)的結果:

$$

quotient = \lfloor \frac{divident}{divisor} \rfloor

$$注:$\lfloor \rfloor$ 為向下取整的符號。如,$\lfloor 3.5 \rfloor = 3$,$\lfloor -1.3 \rfloor = 2$。

15/12 等于 1.25 向下取整就成了 1。-10/12 等于 0.833.. 向下取整為 -1。向下取整可以理解為取一個更靠近負無窮的整數。插一句,C/C++/Java 中,負數除法都是向上取整。也就是 -10/12 等于 0。python 中,負數除法為向下取整。

結合 1 式和 2 式,可以得出:

$$

remainder = divident - divisor \times \lfloor \frac{divident}{divisor} \rfloor

$$

現在將上述代碼可以優化成一句代碼:int one_step_mod(int number, int mod)

{

return number - (mod * (int)floor(number * 1.0 / mod));

}別忘記加上 math.h 頭文件。

另外,試著想想 * 1.0 起什么作用?不加行不行?

借助時鐘的實現解決無符號整型問題

理解了時鐘的運轉,我們再來看看無符號的兩個問題:兩個無符號整數相加,超出了 3 個字節表示的范圍怎么辦?比如:2 + 7。

兩個無符號整數相減。轉換成加上一個負數。既然是無符號整數,哪里來的負數?

如果把 3 個字節的無符號整數看成時鐘的話,它長這樣:

那么無符號整數也同樣可以通過同余運算解決上述問題:

相加超過范圍而導致上溢出

兩個無符號整數相加,如果超出范圍,直接模 2^n^ 即可:

$$

2 + 7 \equiv 9 \bmod 8 \equiv 1 \pmod 8

$$體現在鐘表上,就是將指針順時針旋轉 7 個小時。

加上一個負數

整數相減,相當于加上一個負數。先將負數轉換成 2^n^ 下的最小整數,在進行加法運算:

$$

2 - 7 \equiv 2 + (-7 \bmod 8) \equiv 2 + 1 \equiv 3 \pmod 8

$$體現在鐘表上,就是將指針逆時針旋轉 7 個小時。

總結

計算機用同余運算解決上溢出與負數 。無符號整型的加法運算實際上等價于模 2^n^ 的加法運算。

代碼驗證talk is cheap, show me your code.

講了這么多,再通過代碼來驗證這一過程:

需要注意的是,C 語言中,最小的數據類型為 8 個字節,與上述的 3 個字節有小許差異。int main()

{

uint8_t two = 2;

uint8_t last = 255;

printf("%d \n", two); // 2

printf("%d \n", last); // 255

uint8_t temp = two + last; // 順時針旋轉

printf("%d \n", temp); // 1

temp = two - last; // 逆時針旋轉

printf("%d \n", temp); // 3

temp = -2;

printf("%d \n", temp); // 254

return 0;

}別忘了引入 stdint.h 頭文件。

學完了無符號整數,看看下面這道題你能否給出正確答案:int main()

{

uint8_t a = -128;

uint8_t b = a / -1;

printf("%d", b); // what is it?

return 0;

}

有符號整型(signed integer)

如果僅僅使用符號位來表示 3 個字節的有符號整數,可以表示 2^3^ -1 = 7 個數:000 = (2^1 * 0 + 2^0 * 0) * 1 = 0+0 = 0

001 = (2^1 * 0 + 2^0 * 1) * 1 = 0+1 = 1

010 = (2^1 * 1 + 2^0 * 0) * 1 = 2+0 = 2

011 = (2^1 * 1 + 2^0 * 1) * 1 = 2+1 = 3

100 = (2^1 * 0 + 2^0 * 0) * -1 = -(0+0) = 0

101 = (2^1 * 0 + 2^0 * 1) * -1 = -(0+1) = -1

110 = (2^1 * 1 + 2^0 * 0) * -1 = -(2+0) = -2

111 = (2^1 * 1 + 2^0 * 1) * -1 = -(2+1) = -3

前面說過,如果僅僅使用符號位來表示有符號整型,會有以下問題:0 的表示。

加法無法算出結果。

仔細觀察,你會發現它僅僅可以表示負數,其他什么都干不了:四則運算(也就是加法)會出錯,兩個 0 的問題。

糟糕的表示方法

要想知道它是如何引入這個問題的,將這個糟糕的表示方法轉換成時鐘,可能你就明白了:

從上圖中,我們可以發現,它并不遵循同余運算。仔細觀察 101 也就是 -1 這個地方,在無符號整型中,它表示 5。在有符號整型中,由于最高位被符號位的占據,最大的正數為 011 也就是 3。所以 101 只能表示為負數。為了保證同余運算,只要找到另一個數與 5 關于 8 同余即可。

與 5 關于 8 同余的數有很多:13, 21, -3...。這個數還要滿足一個條件:最大的負數。所以為:5 - 8 = -3。

補碼的引入

通過這種方式,將上面糟糕的時鐘改成遵循同余運算的表示方法:

你可能已經發現了,這套標準就是我們常說的補碼。

因為遵循同余定理,補碼系統已經不存在那兩個問題了:補碼系統中只有一個 0,不存在歧義。

1-1 => 1 + (-1) => 001 + 111 => 000 => 0,加法運算也沒有問題。

所以為什么要有補碼?因為要保證同余運算。而同余運算,就是整數運算的核心原理。

補碼的表示

根據上面的圖,很好表示補碼:正數和零,沒有變化,不用修改。

而負數,比如 -1,就是逆時針轉動一個小時,根據同余定理,逆時針轉動一個小時就代表順時針轉動7個小時:

$$

-1 \equiv 7 \pmod 8

$$

也就是說,當符號位為 1,比如 111,在正整數(7)的基礎上逆時針旋轉 8 個小時就是補碼:補碼

----

000 = (2^2 * 0 + 2^1 * 0 + 2^0 * 0) = 0+0 = 0

001 = (2^2 * 0 + 2^1 * 0 + 2^0 * 1) = 0+1 = 1

010 = (2^2 * 0 + 2^1 * 1 + 2^0 * 0) = 2+0 = 2

011 = (2^2 * 0 + 2^1 * 1 + 2^0 * 1) = 2+1 = 3

100 = (2^2 * 1 + 2^1 * 0 + 2^0 * 0) - 8 = 4-8 = -4

101 = (2^2 * 1 + 2^1 * 0 + 2^0 * 1) - 8 = 5-8 = -3

110 = (2^2 * 1 + 2^1 * 1 + 2^0 * 0) - 8 = 6-8 = -2

111 = (2^2 * 1 + 2^1 * 1 + 2^0 * 1) - 8 = 7-8 = -1

公式表示為:

$$

f(補) =

\begin{cases}

x & 符號位 = 0\\

x-8 & 符號位 = 1

\end{cases}

$$其中,x = 4a + 2b + c。

如果用程序來表示:int main()

{

int n;

puts("Please input a number, represent a few bytes: ");

scanf("%d", &n);

int count = 1 << n;

for (int i = 0; i < count; i++) {

if (i >= (1 << n - 1)) { // 如果這個數的符號位為 1

printf("%d ", i - count);

}

else printf("%d ", i);

}

return 0;

}

反碼(1's complement)的誤區

在上面講解補碼的過程中,并沒有提到反碼。那為什么有些文章說到補碼時,要提到反碼?而且我們常說的反碼加上一等于補碼又是怎么來的,反碼真的可以解決原碼相加的問題嗎?

先來看看反碼的定義:正數的反碼等于其原碼,而負數的反碼則可以通過保留其符號位,將原碼的數值位取反得到。

在 3 個字節的有符號整數中,有:原碼 反碼

---------

000 = 000 = 0

001 = 001 = 1

010 = 010 = 2

011 = 011 = 3

100 = 111 = -0

101 = 110 = -1

110 = 101 = -2

111 = 100 = -3

你會發現,它怎么和僅僅引入符號位的有符號整數那么眼熟。那反碼解決了那兩個問題了嗎?

很顯然的是,反碼中也有兩個零。

那么加法呢?人們最喜歡舉的例子為:

$$

1 - 1 = 1 + (-1) = 001_{正} + 101_{正} = 001_{反} + 110_{反} = 111_{反} = -0 = 0

$$

上面的式子可以運算出正確結果。所以在有些文章中就認為反碼解決了原碼的相加問題。

真的是這樣的嗎?再來看看一個例子:

$$

-1 + (-2) = 101_{正} + 110_{正} = 110_{反} + 101_{反} = 011_{反} = 3

$$

問題出現了,加法運算也不成立。可見,網絡上的文章不太靠譜??次恼?#xff0c;抱著懷疑的態度還是很有必要的。

也就是說,反碼和原碼一樣,并不適合作為有符號整數的表示方法。這也是很多人的誤區,認為反碼與補碼有關系。其實一點關系也沒有,雖然是反碼加上一等于補碼。

那反碼加上一等于補碼,這又是怎么來的呢?

這是一條結論。

反碼加上一等于補碼

在 3 個字節中,原碼使用 abc~2~ 來表示:

$$

f(原) = \begin{cases}

2^{1} \times b + 2^{0} \times c \\

(2^{1} \times b + 2^{0} \times c) \times (-1)

\end{cases} = \begin{cases}

2b + c & a = 0\\

- (2b +c) & a = 1

\end{cases}

$$

原碼轉換成反碼,負數的符號位不變,其他位取反:

$$

f(反) = \begin{cases}

2^{2} \times a + 2^{1} \times b + 2^{0} \times c \\

2^{2} \times a + 2^{1} \times (1 - b) + 2^{0} \times (1 - c)

\end{cases} = \begin{cases}

2b + c & a = 0\\

-(2b +c) + 7 & a = 1

\end{cases}

$$

原碼轉換成補碼。正數不變。負數,比如 -3 就是 0 - 3,現在可以用正數表示 3,而又根據同余定理 0 - 3 等于 8 - 3,所以原碼轉補碼的負數表示方法為:8 - 正數:

$$

f(補) = \begin{cases}

f(原) \\

f(原) + 8

\end{cases} = \begin{cases}

2b + c & a = 0\\

8 - (2b + c) & a = 1

\end{cases}

$$

當符號位為 0,也就是正數的時候,兩者相等。符合正數時,原反補碼相同。

當符號位為 1 ,也就是負數。因為要保證符號位為 1,所以符號位并不參與計算,所以反碼和補碼的計算就轉換成了 2 個字節的無符號加法運算。既然是加法運算,同樣遵循同余運算,這里時關于 4 同余。

$$

f(反) + 1 = -(2b + c) + 8 = -(2b + c) \pmod 4 \qquad 3

$$

$$

f(補) = 8 - (2b + c) = -(2b + c) \pmod 4 \qquad 4

$$

結合 3 式與 4 式,有:

$$

f(反) + 1 = f(補)

$$

所以,反碼加上一等于補碼是這么來的。它并不能作為結論證明反碼和補碼有任何關系,只是可以通過這種方法,在原碼的基礎上快速的得出補碼而已。

代碼驗證int main()

{

int8_t two = 2;

int8_t last = 127;

printf("%d \n", two); // 2

printf("%d \n", last); // 127

int8_t temp = two + last; // 順時針旋轉

printf("%d \n", temp); // -127

temp = two - last; // 逆時針旋轉

printf("%d \n", temp); // -125

return 0;

}

上面的思考題換成有符號整數,還是那個結果嗎?int main()

{

int8_t a = -128;

int8_t b = a / -1;

printf("%d", b); // what is it?

return 0;

}

參考資料

總結

以上是生活随笔為你收集整理的两个负数相减计算机如何表示,计算机如何表示整数的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。