資料整理: jserv
1
,否則為 0
。每個格子的底板可活動,當打開時,圓球會掉落下去。透過這種裝置,他構想出進行二進位數值運算的機械計算機。然而,僅停留在概念階段,萊布尼茲並未繪製實際的設計圖。以二進位來表示數字,以三碼為例,則有 0~7 共 8 個數字可定義。但若要定義負數,則取首碼來表示正負(0 為正、1 為負),後兩碼對應數字,共可定義 -4
~ +3
同樣共 8 個數字;然而其中負數最大的是 -1
,故以 111
訂之,負數中最小的是 -4,以 100
訂之,其餘依大小類推,確保二進位與十進位的大小加減方向相同。而非直接以後兩碼的二進位數字表示。
由上方對稱性可知,例如將 3 的二進位表示 011
完全翻轉成 100
之後,對應的是 -4
(而非 -3
!這是由於為了把 0 定義進來,導致正負數範圍不對稱),因此只要再加 1 上去,就會變成 -3
的二進位表示 101
了!即:
011(+310) ──翻轉─→ 100 (-410) ──再加1─→ 101(-310)
這就是從 +3 取其二補數得到 -3 的過程。
這定義也使得負數自動符合減法運算:只要再加上一個條件「溢位就忽略掉」。運算結果就只會在 -4
~ +3
之間循環。
例如運算 2 + (-3),在二進位的運算即對應 010
+ 101
,這其實在原本二進位的無號數定義中是 2 + 5 的意義。但因溢位就忽略掉造成的循環,我們發現 +5 的運算和 -3 兩者在運算上等價,也就是說,在這些運算之中:
因此原本二進位無號數下半部的加法操作,可以直接照搬過來視為二進位有號數的減法操作。
補充資訊: Bit-Level Representation of Two’s Complement Negation
不是所有的位元組合都能表示合理的數字,存取某些位元組合在特定機器上可能會造成嚴重錯誤,此種組合稱作陷阱表示法 (trap representation)。除非使用位元運算或是違反標準其他規定 (如溢位),一般的運算不可能產生陷阱表示法。
C 語言標準明確允許實作自行決定在以下兩種狀況下是否是陷阱表示法:
位元運算會忽略填充位元,因此(等級不低於 unsigned int 的)無號整數可安心使用。為求最大可攜性,位元運算不應該用在有號整數上。
在 C11 規格 6.2.6.2 Integer types 指出
For unsigned integer types other than unsigned char, the bits of the object representation shall be divided into two groups: value bits and padding bits (there need not be any of the latter). If there are N value bits, each bit shall represent a different power of 2 between 1 and 2N−1, so that objects of that type shall be capable of representing values from 0 to 2N−1 using a pure binary representation; this shall be known as the value representation. The values of any padding bits are unspecified.
uintN_t
和 intN_t
保證沒有填充位元,intN_t
一定是二補數,而且 intN_t
不可能有陷阱表示法,堪稱是最安全的整數型態。實作可能不提供這些型態,不過一旦提供,即保證符合性質。
注意:直到 C23 標準,C 語言才全面採納二補數系統,之前顧及相容性議題,允許一補數的存在。
位移運算子(Shift operator):
x << y
: x 左移 y 位元,左移出的位元會被丟棄,右側會補上 0x >> y
: x 右移 y 位元,右移出的位元會被丟棄。兩種位移:
例:
X = 10100010;
Logical shift: x >> 2
= 00101000
Arithmetic shift: x >> 2
= 11101000
注意位移運算的兩種未定義狀況
int i=0xFFFFFFFF;
i = i << 32; // 此結果未定義
算術位移的應用,若要判斷一個 int 型態的變數 n
是否為正數,可用 n >> 31
其等價於 n >=0 ? 0: -1
.
有號數 x 到無號數 (U 結尾) 映射機制:非負數不變為 x,負數轉換為大的正數 (x + 2w,最靠近 0 的負數映射最大,最小的負數被映射為剛好在二補數表達之外的無符號數)。
1000
] ~ 7 [0111
]當無號數與有號數在 C 語言混合在單一表示式時,有號數會被轉換為無號數。在運算子操作時會有些意外的結果。有號數負值會因為轉換成 2 補數後,反而負值會大於正值。因此要注意混用時的運算狀況。
案例:
變數 1 | 變數 2 | 比較結果 | 轉換後 |
---|---|---|---|
0 | 0U | == | unsigned |
-1 | 0 | < | signed |
-1 | 0U | > | unsigned |
2147483647 | -2147483647-1 | > | signed |
2147483647U | -2147483647-1 | < | unsigned |
-1 | -2 | > | signed |
(unsigned)-1 | -2 | > | unsigned |
2147483647 | 2147483648U | < | unsigned |
2147483647 | (int) 2147483648U | > | signed |
一個有號數意外結果的例子。
int n = 10;
for (int i = n - 1 ; i - sizeof(char) >= 0; i--)
printf("i: 0x%x\n",i);
這個例子會造成無窮迴圈,因為 sizeof
會回傳值是 unsigned int 型態,i 變數也會被轉換為 unsigned 的形式,無號數 0 再減 1 就會變為 0xFFFFFFFF
而產生無窮迴圈。
在有號整數上都可能產生陷阱表示法
補充資訊: Writing TMin in C
延伸閱讀: Signed and Unsigned Numbers
在 Whirlwind Tour of ARM Assembly 指出,若 n 是有號 32-bit 整數,那麼 n >> 31
相當於 n >= 0 ? 0 : -1
若 n 是 32-bit 整數,那麼 abs(n)
等同於 ((n >> 31) ^ n) - (n >> 31)
n >> 31
是 0;n ^ 0
是 n - 0
仍是 n >> 31
是 -1;-1
以 2 補數表示為 0xFFFFFFFF
;n ^ (-1)
等同於 1 補數運算;-1
,得到 2 補數運算的值例如: unsigned int to unsigned short
補充資訊: More on Boolean Algebra and Boolean Rings
用 XOR 檢查加法後是否有溢位
long a, b, x;
x = a + b;
if ((x ^ a) >= 0 || (x ^ b) >= 0)
...
檢查是否有加法導致的溢位:
除了這兩者外沒有其它情況。綜合上述的條件,可簡化成:兩數相加後的正負號和原本兩者都不同
採用二補數系統,最高 bit 作為是 sign bit,XOR 剛好可滿足簡化的條件,其它 bit 無關緊要,運算完看 sign bit 即可。
一補數具有兩個 0 (即 +0
和 -0
),加法器進行減法時,往往需要一個額外的步驟,調整為單一表示法。二補數即可避免這問題,因此現代資訊系統無論是程式語言的整數表示或加法器的實作,幾乎皆採用二補數表示法。
在運算表達中,倘若其中一個為有號型態 (signed),編譯器會 implicitly 將有號轉為無號運算。
-1 < 0U
為 False-2147483647 - 1
(轉換為無號數相當於 232)== 2147483648U
為 True這種轉換帶來的錯誤有時很難發現,避免的方法是,不該濫用無號數。
for (unsigned i= cnt - 2; i >= 0; i--)
a[i] += a[i+1];
#define DELTA sizeof(int)
for (int i= CNT; i - DELTA >= 0; i-= DELTA)
for (unsigned i = cnt - 2; i < cnt; i--)
a[i] += a[i + 1];
C Standard guarantees that unsigned addition will behave like modular arithmetic
0 –1
UMaxImage Not Showing Possible ReasonsLearn More →
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
最好改為:
for (size_t i = cnt - 2; i < cnt; i--)
a[i] += a[i + 1];
size_t
定義為 unsigned 並與 word size 等寬cnt
= UMax 時也能運作cnt
有號且 < 0
呢?int 只保證能存下 -215 + 1 (-32767) 到 215 - 1 (32767) 之間的整數,16 位元已足夠
int_least32_t 和 int_fast32_t (C99 開始引入的 fixed width integer types) 可保證存下至少 -231 + 1 到 231 - 1 之間的整數
bi-endianness (沒打錯,是 bi,而非 "big")
宋代學者趙與時在《賓退錄》說:「讀諸葛孔明《出師表》而不墮淚者,其人必不忠」,台灣人宅色夫在「Linux 核心設計」說:「讀 CS:APP 而不墮淚者,其人必不智」
終於知道為何我們研究生追求達到 CMU 大學部二年級程度是多麽偉大的目標吧?
tadd_ok
(Page 65)#include <stdbool.h>
bool tadd_ok(int x, int y) {
int sum = x + y;
bool over_neg = (x < 0) && (y < 0) && (sum >= 0);
bool over_pos = (x >= 0) && (y >= 0) && (sum < 0);
return (!over_neg) && (!over_pos);
}
導讀
「有理數」詞源
Floating Point
rounding error
3 種浮點型態
"Normalized" Values
When: exp≠ 000…0 and exp≠ 111…1
Denormalized Values
Condition: exp = 000…0
Exponent value: E= 1 –Bias (instead of exp–Bias)
Special Values
Condition: exp= 111…1
IEEE 754 浮點數值的比較,需要考慮到 sign + magnitude,實際操作類似以下:
#define FasI(f) (*((int *) &(f)))
#define FasUI(f) (*((unsigned int *) &(f)))
#define lt0(f) (FasUI(f) > 0x80000000U)
#define le0(f) (FasI(f) <= 0)
#define gt0(f) (FasI(f) > 0)
#define ge0(f) (FasUI(f) <= 0x80000000U)
is_little_endian
,在 Little endian 機器上返回 1
,反之返回 0
,應能在任意機器、無論幾位元的架構都適用 (Page 88)leftmost_one
,產生得以找出數值 x 最高位 1
的位元遮罩 (Page 90)