[英]Overflow and underflow in unsigned integers
假設,我試圖減去 2 個無符號整數:
247 = 1111 0111
135 = 1000 0111
如果我們減去這 2 個二進制數,我們得到 = 0111 0000
這是下溢嗎,因為我們現在只需要 7 位? 或者它是如何工作的?
下溢的無符號減法c = a - b
每當發生b
大於a
。
然而,這有點循環定義,因為有多少種機器執行a < b
比較是通過使用環繞算法減去操作數,然后根據兩個操作數和結果檢測溢出。
另請注意,在 C 中我們不談論“溢出”,因為沒有錯誤條件:C 無符號整數提供了硬件中常見的環繞算法。
因此,鑒於我們有環繞算法,我們可以檢測在減法中是否發生了環繞(或溢出,取決於觀點)。
我們需要的是a
、 b
和c
最重要的位。 我們稱它們為A
、 B
和C
。 根據這些,溢出V
計算如下:
A B C | V
------+--
0 0 0 | 0
0 0 1 | 1
0 1 0 | 1
0 1 1 | 1
1 0 0 | 0
1 0 1 | 0
1 1 0 | 0
1 1 1 | 1
這簡化為
A'B + A'C + BC
換句話說,無符號減法c = a - b
中的溢出發生在:
a
為0,並且的b
為1;a
為0,並且的c
是1;b
的最高位元為 1,而c
的最高位元也為 1。減去 247 - 135 = 112 顯然不會溢出,因為 247 大於 135。應用上面的規則,A = 1,B = 0 和 C = 0。表的 1 1 0 行在 V 列中有一個 0 : 沒有溢出。
通常,“下溢”意味着計算的理想數學結果低於該類型可以表示的結果。 如果在無符號算術中從 5 中減去 7,理想的數學結果將是 -2,但無符號類型不能表示 -2,因此運算下溢。 或者,在可以表示從 -128 到 +127 的數字的八位有符號類型中,從 -100 中減去 100 會產生 -200,但這無法在類型中表示,因此運算下溢。
在 C 中,無符號算術被稱為不會下溢或溢出,因為 C 標准定義了要使用模算術而不是實數算術執行的運算。 例如,對於 32 位無符號算術,從 5 中減去 7 將產生 4,294,967,294(十六進制為 FFFFFFFE 16 ),因為它包含了模 2 32 = 4,294,967,296。 盡管如此,人們在討論這些操作時可能會使用術語“下溢”或“溢出”,旨在指代數學問題而不是定義的 C 行為。
換句話說,對於您用於算術的任何類型,都有該類型可以表示的某個下限L和某個上限U。 如果運算的理想數學結果小於L ,則運算下溢。 如果運算的理想數學結果大於U ,則運算溢出。 “下溢”和“溢出”意味着操作已經超出了類型的界限。 “溢出”也可用於指任何超出類型邊界的情況,包括在低方向。
這並不意味着需要更少的位來表示結果。 當從 11110111 2 中減去 10000111 2 時,結果 01110000 2 = 1110000 2在界限內,因此沒有上溢或下溢。 它需要更少的位來表示這一事實無關緊要。
(注意:對於整數算術,“下溢”或“溢出”是相對於絕對邊界L和U 定義的。對於浮點算術,這些術語的含義有些不同。它們可能是相對於結果的大小來定義的,忽略符號,它們是相對於格式的有限非零范圍定義的。浮點格式可能能夠表示 0,然后是各種有限的非零數,然后是無窮大。0 和最小之間的某些結果格式可以表示的非零數被稱為下溢,即使它們在技術上處於可表示數的范圍內,該范圍是從 0 到無窮大。類似地,某些高於最大可表示有限數的結果被稱為溢出,即使它們在可表示范圍內,因為它們小於無窮大。)
長話短說,這就是當您有以下情況時會發生的情況:
unsigned char n = 255; /* highest possible value for an unsigned char */
n = n + 1; /* now n is "overflowing" (although the terminology is not correct) to 0 */
printf("overflow: 255 + 1 = %u\n", n);
n = n - 1; /* n will now "underflow" from 0 to 255; */
printf("underflow: 0 - 1 = %u\n", n);
n *= 2; /* n will now be (255 * 2) % 256 = 254;
/* when the result is too high, modulo with 2 to the power of 8 is used */
/* for an 8 bit variable such as unsigned char; */
printf("large overflow: 255 * 2 = %u\n", n);
n = n * (-2) + 100; /* n should now be -408 which is 104 in terms of unsigned char. */
/* (Logic is this: 408 % 256 = 152; 256 - 152 = 104) */
printf("large underflow: 255 * 2 = %u\n", n);
結果是(用 gcc 11.1 編譯,標志 -Wall -Wextra -std=c99):
overflow: 255 + 1 = 0 underflow: 0 - 1 = 255 large overflow: 255 * 2 = 254 large underflow: 255 * 2 = 104
現在是科學版本:上面的評論僅代表正在發生的事情的數學模型。 為了更好地理解實際發生的情況,以下規則適用:
當對它們執行操作時,會提升小於 int 的整數類型。 如果原始類型的所有值都可以表示為int,則將較小類型的值轉換為int; 否則,它被轉換為無符號整數。
那么當計算機n = 255; n = n + 1;
時,內存中實際發生了什么n = 255; n = n + 1;
n = 255; n = n + 1;
是這樣的:首先,右邊被評估為一個int(有符號),因為根據整數提升的規則,結果適合一個有符號的int。 所以表達式的右邊變成了二進制: 0b00000000000000000000000011111111 + 0b00000000000000000000000000000001 = 0b00000000000000000000000100000000
位
將 32 位 int 分配回 8 位數字時,會丟失最高有效的 24 位。
因此,當將0b00000000000000000000000100000000
分配給變量 n(一個無符號字符)時,32 位值被截斷為 8 位值(僅復制最右邊的 8 位)=> n 變為0b00000000
每個操作都會發生同樣的事情。 右側的表達式計算為有符號整數,然后將其截斷為 8 位。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.