Chapter 2
# 信息的表示和處理 Bits Bytes & Intergers
## 位 整數(shù)
**為啥要用二進(jìn)制?二進(jìn)制有什么優(yōu)點(diǎn)?在現(xiàn)實(shí)中有哪些應(yīng)用?**
在計(jì)算機(jī)中,數(shù)據(jù)是以位/比特(bit)為單位儲(chǔ)存的,一個(gè)字節(jié)是8bit的數(shù)據(jù)塊,這個(gè)尺度是計(jì)算機(jī)設(shè)計(jì)早期確定的,所以沿用至今(_為什么這么設(shè)計(jì)?_ASCII `linux: man ascii`)
進(jìn)制轉(zhuǎn)換的操作要會(huì)哦,尤其是2進(jìn)制,10進(jìn)制,16進(jìn)制的相互轉(zhuǎn)化;其中二進(jìn)制數(shù)存在最高有效位(MSB),最低有效位(LSB),十六進(jìn)制和二進(jìn)制轉(zhuǎn)化極為方便.
> 這里我先插一嘴(哇哦)正蛙,我們要熟練掌握十六進(jìn)制和二進(jìn)制之間的轉(zhuǎn)化
**字長(zhǎng)(word size)**對(duì)于計(jì)算機(jī)內(nèi)存而言是很重要的概念,它標(biāo)識(shí)了的內(nèi)存地址的最大長(zhǎng)度,而**地址的表示范圍決定了內(nèi)存的上限**.我們常說(shuō)的 32 位系統(tǒng),其內(nèi)存地址空間最大為 4GB,不管你裝多少根內(nèi)存,能使用的就這么多,這是系統(tǒng)層面的限制,而更高級(jí)的 64 位系統(tǒng),其內(nèi)存上限達(dá)到 16EB,以人類目前的生產(chǎn)力,都很難湊出來(lái)這么大的內(nèi)存.目前銷售的計(jì)算機(jī),絕大多數(shù)都是 64 位系統(tǒng)了.
> 32位系統(tǒng)不能運(yùn)行64位程序,但是多虧AMD的x86-64架構(gòu),64位系統(tǒng)是可以運(yùn)行32位程序的.
由于在不同編譯器和系統(tǒng)上,int類型的長(zhǎng)度可能會(huì)不同,C99中解決了這個(gè)問(wèn)題,規(guī)定`int32_t`和`int64_t`來(lái)分別表示長(zhǎng)度為4和8的整型數(shù)值,但是long的數(shù)據(jù)長(zhǎng)度還是會(huì)根據(jù)32/64位系統(tǒng)變化.
計(jì)算機(jī)歷史上還有大小端的派別之分,大端即數(shù)據(jù)高位在小地址,低位在大地址,反之則為小端法.
而且Windows,Linux,IOS和Android都是小端法.
ANSCII只適合表示英文文本內(nèi)容,對(duì)于特殊符號(hào)很乏力,所以衍生了Unicode這一規(guī)范,后來(lái)的語(yǔ)言像java都支持Unicode文本編碼,當(dāng)然C也有補(bǔ)救的庫(kù).
接下來(lái)辨析兩組設(shè)定:定點(diǎn)數(shù)和浮點(diǎn)數(shù)
計(jì)算機(jī)用二進(jìn)制如何表示小數(shù)呢?要么固定小數(shù)點(diǎn)位置,固定讀取,要么再花空間儲(chǔ)存動(dòng)態(tài)小數(shù)點(diǎn)的位置,這就是頂點(diǎn)和浮點(diǎn)的定義來(lái)源.定點(diǎn)整數(shù)就是整數(shù)啦,分為有符號(hào)和無(wú)符號(hào),而小數(shù)(浮點(diǎn)數(shù))的表示稍微復(fù)雜一點(diǎn)(后述).
布爾大兄弟發(fā)明的布爾代數(shù),邏輯系統(tǒng),增加了四類運(yùn)算:
- 與`And`’&’
- 或`Or`’|’
- 非`Not` ‘'
- 異或`Xor` ‘^’
? ? 異或這個(gè)東西有很多驚奇的用法,比如只用異或可以實(shí)現(xiàn)兩變量交換值(注意!!如果x y數(shù)值相等,則結(jié)果為0);
```c
*x = *x ^ *y;
*y = *x ^ *y;
*x = *x ^ *y;
```
但是記得和邏輯運(yùn)算符區(qū)分開(kāi),位運(yùn)算符都是單符號(hào)運(yùn)算符,邏輯運(yùn)算符都是雙符號(hào)的(除了非是!)
位運(yùn)算還有兩個(gè)小家伙,是左移和右移<<>>,就是將二進(jìn)制數(shù)的小數(shù)點(diǎn)左右移動(dòng),相當(dāng)于原數(shù)據(jù)除以/乘以2.位運(yùn)算比乘法更快,可以作為特別情況下的簡(jiǎn)化.右移存在邏輯右移和算術(shù)右移,算術(shù)右移會(huì)按照最高位數(shù)值復(fù)制補(bǔ)位,而邏輯右移就是補(bǔ)0(大多數(shù)情況下,右移使用邏輯移位).
要注意,如果移位數(shù)量大于目標(biāo)數(shù)據(jù)長(zhǎng)度,編譯器會(huì)先對(duì)移位數(shù)量取模(mod by length of target),用取模結(jié)果再進(jìn)行移位.
C語(yǔ)言中各類數(shù)據(jù)的寬度

### 整型數(shù)
有無(wú)符號(hào)數(shù)唯一的區(qū)別就是符號(hào)位,無(wú)符號(hào)數(shù)全部寬度都用于儲(chǔ)存數(shù)據(jù)內(nèi)容,有符號(hào)數(shù)的最高位被用作符號(hào)位,導(dǎo)致表示范圍減半.
在儲(chǔ)存負(fù)數(shù)時(shí),有符號(hào)數(shù)不是單純的符號(hào)位置1,而是使用補(bǔ)碼,即對(duì)應(yīng)正數(shù)的二進(jìn)制形式反轉(zhuǎn)再+1,就得到對(duì)應(yīng)負(fù)數(shù)的補(bǔ)碼.**補(bǔ)碼使符號(hào)位能與有效值部分一起參加運(yùn)算,從而簡(jiǎn)化運(yùn)算規(guī)則,并使減法運(yùn)算轉(zhuǎn)換為加法運(yùn)算,進(jìn)一步簡(jiǎn)化計(jì)算機(jī)中運(yùn)算器的線路設(shè)計(jì).**
三碼概念辨析:
- 原碼:符號(hào)位+數(shù)值絕對(duì)值
- 反碼:原碼中符號(hào)位不變,其他數(shù)值位和符號(hào)位做異或(即正數(shù)反碼和原碼一樣,負(fù)數(shù)數(shù)值位會(huì)反轉(zhuǎn))
- 補(bǔ)碼:正數(shù)的補(bǔ)碼與其原碼相同(正數(shù)三碼合一),負(fù)數(shù)是反碼的最低位+1.
有無(wú)符號(hào)數(shù)的轉(zhuǎn)換:強(qiáng)制保位轉(zhuǎn)換/軟式保值轉(zhuǎn)換,保位轉(zhuǎn)換就是位模式不變,重新解讀數(shù)值(按目標(biāo)編碼類型的規(guī)則解讀),可能會(huì)有意外副作用:數(shù)值被+/-2^w
**當(dāng)計(jì)算表達(dá)式中既有有符號(hào)數(shù)又有無(wú)符號(hào)數(shù)時(shí),有符號(hào)自動(dòng)強(qiáng)制轉(zhuǎn)化為無(wú)符號(hào)數(shù),這就會(huì)挖很多大坑,像一些宏變量都是unsigned int;另一些循環(huán)里涉及負(fù)數(shù)時(shí),如果遇到無(wú)符號(hào)邊界就可能出現(xiàn)奇怪的循環(huán)現(xiàn)象**
但是可以利用這個(gè)特性去做一些技巧實(shí)現(xiàn),比如向下計(jì)數(shù):
```c
unsigned i; 或者 size_t i;
for (i = cnt-2; i < cnt; i--)
a[i] += a[i+1];
```
**因此除非必要,建議初始化變量時(shí)都用unsigned數(shù)據(jù)**
有的時(shí)候數(shù)據(jù)寬度不夠用了,那就得拓展一下子:符號(hào)拓展.無(wú)符號(hào)數(shù)直接左邊高位填0擴(kuò)展;有符號(hào)數(shù)進(jìn)行拓展,從短整數(shù)類型向長(zhǎng)整數(shù)類型轉(zhuǎn)換時(shí),C自動(dòng)進(jìn)行符號(hào)擴(kuò)展.
有的時(shí)候不小心從長(zhǎng)變短,就得截?cái)嘁幌?疼),那么多出的位直接截?cái)?變量值根據(jù)剩余位數(shù)據(jù)重新解讀,基本就是求模了.
可以再瞅瞅運(yùn)算:
- 加:可能存在溢出,就要丟棄溢出位數(shù)據(jù)(忽略進(jìn)位輸出),有符號(hào)數(shù)還存在正/負(fù)溢出,減法合并到加法.
- 非:這沒(méi)啥說(shuō)的吧
- 乘:依然,溢出就丟棄,可以理解為運(yùn)算完又做了一次取模,想要完整數(shù)據(jù)結(jié)果,需要使用一些特殊的包(_看看寄存器是怎么解決乘法溢出的?_)
- **除**:當(dāng)除數(shù)為2的n次冪,那就右移n位即可;如果不是,就需要舍入除法(實(shí)際結(jié)果為浮點(diǎn)數(shù),舍入后變?yōu)檎麛?shù),避免類型不一致):?
對(duì)于無(wú)符號(hào)數(shù)和有符號(hào)正數(shù),其實(shí)直接移位就可以23333,但是如果是有符號(hào)負(fù)數(shù),需要先加一個(gè)偏移量(2^k-1),然后再右移n位,這樣保證截?cái)嗪笫窍蛄闵崛?即:?
`x / k = ( x + (1<<k)-1 ) >> k`
## 浮點(diǎn)數(shù)
浮點(diǎn)數(shù)這個(gè)東西十分滴有趣,由于小數(shù)點(diǎn)會(huì)自己動(dòng),可以表示非常大的數(shù)或者非常接近零的數(shù),但是本質(zhì)上還是2為基的一種表示方式,所以很多小數(shù)只能通過(guò)多位逼近表示其近似值,像0.27這種數(shù).
IEEE(Institute of Electrical and Electronics Engineers)電子和電氣工程師協(xié)會(huì)統(tǒng)一了規(guī)范,設(shè)置了IEEE 754這樣的浮點(diǎn)規(guī)范:(有關(guān)IEEE 754的詳細(xì)介紹 [點(diǎn)擊鏈接](http://c.biancheng.net/view/314.html))
一個(gè)浮點(diǎn)數(shù)由符號(hào)(sign),尾數(shù)(mantissa/fraction/significand),基(base),階(exponent)和一個(gè)點(diǎn)構(gòu)成,對(duì)于計(jì)算機(jī)的浮點(diǎn)小數(shù),可以用2位基的不科學(xué)計(jì)數(shù)法表示:Mx2^E.
在32/64位浮點(diǎn)數(shù)儲(chǔ)存的格式不同,float浮點(diǎn)數(shù)中,第0位是符號(hào)位,1-8位是階碼位,9-31是尾數(shù)位;在double中,0是負(fù)號(hào),1-11是階碼,12-63是尾數(shù)
但是你想得太簡(jiǎn)單了!(我也天真了),他們分了規(guī)格化數(shù)和非規(guī)格化數(shù),咱摳一摳吧:
- **規(guī)格化數(shù)**:浮點(diǎn)數(shù)階碼位不全為0/1時(shí)為規(guī)格化數(shù)范圍?
但是階碼采用偏置值編碼,exponent=E+Bias,其中Bias的值在32/6位浮點(diǎn)數(shù)中分別為127/1023.?
尾數(shù)編碼時(shí),默認(rèn)第一位為1(二進(jìn)制),這樣節(jié)省了一位精度,只需要表示M中小數(shù)點(diǎn)后面的數(shù)據(jù)即可,當(dāng)frac段為全0時(shí),這個(gè)M就是1(最小的規(guī)格化數(shù)),當(dāng)frac全為1時(shí),M趨近于2,是最大值.
- **非規(guī)格化數(shù)**:這個(gè)老哥可以表示規(guī)格化數(shù)范圍外的數(shù)據(jù),很有用.?
exp=000…000時(shí),**E=1-Bias**,尾數(shù)里默認(rèn)第一位是0,這樣就能表示0附近的數(shù)(lim-\>0)了,此時(shí)又會(huì)出來(lái)一對(duì)正負(fù)0.?
exp=111…111時(shí),當(dāng)frac=0 就可以表示正負(fù)無(wú)窮(除零錯(cuò)誤),如果frac!=0,那輸出的就是NaN了

數(shù)說(shuō)完了,那咋計(jì)算呢?浮點(diǎn)數(shù)運(yùn)算有個(gè)基本思想,就是先算精確值,然后根據(jù)輸出變量類型進(jìn)行相應(yīng)轉(zhuǎn)化,是舍入還是溢出了etc.
說(shuō)到舍入呢,有四種舍入方法,最簡(jiǎn)單的倆就是向上舍入和向下舍入,不管小數(shù)位是啥直接進(jìn)位或者舍去,另外還有向零舍入和向偶數(shù)舍入.最后這個(gè)**向偶數(shù)舍入**是重點(diǎn),也是默認(rèn)舍入模式.
**偶數(shù)舍入**:當(dāng)小數(shù)不等于中間值時(shí)就采用向上或者向下舍入,等于中間值的時(shí)候判斷哪邊舍入之后變成偶數(shù)形式(小數(shù)最后一位為偶數(shù),二進(jìn)制時(shí)舍入位右側(cè)全為0)即可.
回到計(jì)算,我們只需要關(guān)注乘法和加法就可以,其他的運(yùn)算大同小異.
- **乘法**:階碼相加尾數(shù)相乘,這個(gè)不用多說(shuō),數(shù)學(xué)知識(shí).當(dāng)尾數(shù)相乘后\>2了,那就進(jìn)個(gè)位,右移一下,E+1就完事了;但是也有可能溢出,乘積太大了唄,那就會(huì)報(bào)錯(cuò)了.運(yùn)算最后肯定加一步舍入,這個(gè)前面剛說(shuō).
- **加法**:首先得對(duì)齊小數(shù)點(diǎn)再相加,結(jié)果的階碼等于加數(shù)里最大的E(對(duì)齊小數(shù)點(diǎn)了嘛),進(jìn)位溢出舍入的操作和乘法都相似.
前面說(shuō)的都是浮點(diǎn)數(shù)運(yùn)算的通用規(guī)則和設(shè)定,在C語(yǔ)言中還有一些注意事項(xiàng):
1. int轉(zhuǎn)float,不會(huì)溢出,但是可能舍入
2. 低精度轉(zhuǎn)高精度,不會(huì)丟數(shù)據(jù)(廢話),反過(guò)來(lái)可能造成溢出/舍入,丟數(shù)據(jù)(還是廢話)
3. 從浮點(diǎn)直接轉(zhuǎn)int的話,就咔嚓一下截?cái)嗔?/p>
## 內(nèi)存 指針 字符串
內(nèi)存就是個(gè)超大數(shù)組,線性存著一大堆數(shù)據(jù),內(nèi)存地址就是數(shù)組的索引,指針就是索引的值.從8章可知,其實(shí)每個(gè)進(jìn)程都有屬于自己獨(dú)占的內(nèi)存空間,高度抽象的進(jìn)程管理方式.
字長(zhǎng):地址數(shù)據(jù)寬度,如果是32位地址,那內(nèi)存最大4G(32位系統(tǒng)內(nèi)存上限),64位地址,那內(nèi)存上限老大了(懶得算),計(jì)算機(jī)目前使用的是面向”字”的內(nèi)存組織管理,每個(gè)字占4/8字節(jié)
字節(jié)排列順序(從小到大/從大到小)就分為小端法和大端法,這個(gè)隨操作系統(tǒng)變化,可以利用指針/共用體輸出一個(gè)變量的低位/高位數(shù)據(jù)值來(lái)判斷.
字符串就是ASCII碼表示咯!