斯坦福編程范式第二課筆記(數(shù)據(jù)類型在內(nèi)存中的表示)

斯坦福編程范式第二課筆記(數(shù)據(jù)類型在內(nèi)存中的表示)

內(nèi)存的最小單位是字節(jié),一個字節(jié)等于8位(bit),每一位要么是0要么是1,也就是用二進(jìn)制來表示。

一個字節(jié)在內(nèi)存中的表示為:

image

無符號整數(shù)的表示

無符號二進(jìn)制轉(zhuǎn)成十進(jìn)制公式:

image
  • w:二進(jìn)制位的長度。
  • i:二進(jìn)制位從右往左開始的下標(biāo),從0開始計數(shù)。
  • w-1:由于i是從0開始計數(shù),所以最后一個下標(biāo)就是w-1。
  • x(i):第i位的值,要么是0要么是1。
  • 2^i:2的第i次冪。

例如:
無符號二進(jìn)制數(shù)10010 按照公式展開就是:

image

如果把這個數(shù)用1個字節(jié)在計算機(jī)中存儲,內(nèi)存中就表示為:

image

不足8位,左邊補(bǔ)0。

1個字節(jié)的無符號正式能表示2^8 = 256個不同的數(shù)。能表示最大的數(shù)是8個二進(jìn)位全是1的數(shù)等于255,也就是求一個公比為2,首項是1等比數(shù)列前8項和。二進(jìn)制位求和公式為(2^n) - 1。總結(jié)下來一個n位的二進(jìn)制數(shù)能表示最大的數(shù)是(2^n) - 1,能夠表示2^n個不同的數(shù),之所以是2^n個不同的數(shù),是因為可以表示0~(2^n) - 1,從0開始的所以還需要+1個長度。

Char在內(nèi)存中的表示

Char類型是用來存儲單個字符,在內(nèi)存中占用1個字節(jié)的大小,它使用8個bit來表示256個字符。
Char類型實際存儲的是字符的ASCII碼,由于ASCII碼是整數(shù)。所以Char最終在內(nèi)存中是一個8bit的整型。

比如字符AASCII碼是65,65 = 2^0 + 2^6,所以在內(nèi)存中的表示為:

image

char ch = 'A';
printf("%d", ch); // output is 65

Short在內(nèi)存中的表示

Short 表示的是短整型,一般占用2個字節(jié)的內(nèi)存大小。

它的取值范圍是(-2)^15~(2^15)-1包含0。最大值這里是(2^15)-1,是因為short有符號位,需要用最高位(用從左到右第一位)來表示符號,0表示正數(shù),1表示負(fù)數(shù)。 最大值的二進(jìn)制表示為0111111111111111(16個二進(jìn)制位),十進(jìn)制就是(2^15)-1。 之所以是(2^15)-1,也是之前說的求和公式((2^n)-1

實現(xiàn)加減法

二進(jìn)制加減法和十進(jìn)制一樣,把對應(yīng)相加,大于1就向前進(jìn)位。例如0111 + 1 = 1000

如果想要把7和-7相加使結(jié)果等于0。按照在計算機(jī)中使用二進(jìn)制的最高位來當(dāng)做符號位的,0表示正數(shù),1表示負(fù)數(shù)。那么7表示為0000111,-7就表示為1000111 。0000111 + 1000111 按照二進(jìn)制先前的加法法則得出來是1001110,結(jié)果不是我們想要的0。

怎么才能讓2個二進(jìn)制數(shù)相加得到0呢?

想要得到0,就需要利用進(jìn)位,比如在11111111(8個1)的基礎(chǔ)上加1就可以得到100000000(一共9位,左邊第一位是1,后面8個0) ,舍掉最左邊的那個1就得到了8個0最終結(jié)果就等于0。把原碼按位取反然后與原碼相加就可以得到全1的二進(jìn)制數(shù)。比如0000111按位取反就是1111000,他們倆相加得到11111111。 再把它加1就得到最后的結(jié)果0。整個過程需要3步,我們把最后兩步合并成一個步驟,也就是把按位取反和加1合并到一起,其實就是把原碼的反碼加1。如1111000加1得到1111001。最后這兩步合在一起叫做取原碼的補(bǔ)碼。最后得到的1111001就叫做0000111的補(bǔ)碼。

  • 正整數(shù)的補(bǔ)碼是其本身。
  • 負(fù)整數(shù)的補(bǔ)碼是把它對應(yīng)的正整數(shù)二進(jìn)制碼按位取反,也就做原碼的反碼然后再加1。

比如正整數(shù)7的二進(jìn)制碼是0000111,它的補(bǔ)碼還是它本身。再比如-7對應(yīng)的正整數(shù)二進(jìn)制碼是0000111,它的反碼就是1111000(把原碼按位取反)。然后再加1就得到1111001。1111001就是-7的補(bǔ)碼。我們再次把11110010000111按照二進(jìn)制加法法則相加剛好得到0。這里需要注意的是,這里左邊會產(chǎn)生一個溢出位,這個溢出位是去掉不要的,得到結(jié)果就是0。

-1的補(bǔ)碼全是1,因為它加上1之后就變成了0。

計算機(jī)系統(tǒng)都是用補(bǔ)碼來表示二進(jìn)制碼,這樣的好處之一就是可以讓加減法運(yùn)算統(tǒng)一處理。

位模式拷貝

當(dāng)把char類型的變量賦值給short類型的變量時,會把char的8個bit放在short的低八位(從右往左第一個字節(jié))上。

例如:

char ch = 'A'; // 'A' ASCII:65 內(nèi)存表示為 01000001
short s = ch; // 內(nèi)存表示為 00000000 | 01000001

一個特殊的情況就是當(dāng)把一個short-1賦值給一個int變量的時候,并不會得到00000000 | 00000000 | 11111111 | 11111111,因為如果這樣的話表示的值就不是-1了。所以正確的做法就是把所有的1全部拷貝給int。
例如:

short s = -1; // 內(nèi)存表示為 11111111 | 11111111
int i = s; // 內(nèi)存表示為 11111111 | 11111111 | 11111111 | 11111111

相反如果把short類型的變量賦值給char類型的變量時,會把short的低八位(從右往左第一個字節(jié))放在char僅有的一個字節(jié)上。會把多的字節(jié)自動剔除。
例如:

short s = 65; // 內(nèi)存表示為 00000001 | 01000001
char ch = s; // 內(nèi)存表示為 01000001

浮點(diǎn)數(shù)的表示

我們已經(jīng)知道無符號二進(jìn)制轉(zhuǎn)成十進(jìn)制公式為:

image

這里的i是從0開始的也就是從右邊的第一位是2^0,如果我們從一個負(fù)整數(shù)開始的話,就會存在負(fù)整數(shù)次冪,那么也就會出現(xiàn)小數(shù)部分了。
例如有一個16位的二進(jìn)制數(shù)000000011 | 11000000 用它的前八位來表示整數(shù)部分,后八位來表示小數(shù)部分,就也可以這樣表示000000011.11000000。這樣后八位也就不再是整數(shù)次冪了,而是從左到右每一位分別是2^(-1)~2^(-8)。這個數(shù)就可以表示成:

image

這是其中一種浮點(diǎn)數(shù)表示方法,這種方法表示的浮點(diǎn)數(shù)會出現(xiàn)精度不夠,表示的數(shù)值區(qū)間比較小,所以計算機(jī)實際并沒有用該方法來表示浮點(diǎn)數(shù)。

下面這種方法就是計算機(jī)內(nèi)部真實表示浮點(diǎn)數(shù)的方法。

我們先來看下十進(jìn)制的科學(xué)計數(shù)法,用科學(xué)計數(shù)法表示123.45的話就是1.2345 * 10^2。其中1.2345 為尾數(shù),10為基數(shù),2為指數(shù)。計算機(jī)在表示浮點(diǎn)數(shù)的時候,也借用了十進(jìn)制的科學(xué)計數(shù)法的思想,只不過基數(shù)為2了。

例如1000.01 可以表示成1.00001 * 2^3,幾次冪,小數(shù)點(diǎn)就向右移動幾位。

32位float來舉例,首位是符號位S,緊跟后面8位是指數(shù)位E,最后23位稱為尾數(shù)位M

計算公式:

image
  • S:符號位

S為0時剛好是正數(shù),為1時是負(fù)數(shù)。

  • M:尾數(shù)部分

它的取值范圍是1≤M<2,取值方式是從左到右每一位分別表示的是2^-1~2^-23,值就是然后對各個位的表示值求和,這里跟先前浮點(diǎn)數(shù)表示的辦法一致,都是從負(fù)整數(shù)次冪開始。由于尾數(shù)的整數(shù)部分始終都是1,所以這個1可以被省略,這樣就可以多出一位來提升精度。

  • E:指數(shù)部分

減去127是因為偏移量是127。

例如0 | 10000010 | 11110000000000000000000的每一部分別是:

  • S:0

表示整數(shù)。

  • M:11110000000000000000000

這里需要再加1,因為為了提升小數(shù)精度省略了1,所以要加回來。所以完整的尾數(shù)部分應(yīng)該是1.1111(省略了后面的0)。 2^0 + 2^-1 + 2^-2 + 2^-3 + 2^-4 = 1.9375

  • E:10000010

2^7 + 2^1 = 130

分別帶入公式得:

二進(jìn)制形式:

image

十進(jìn)制形式:

image

詳細(xì)過程:
1 * 1.1111 * 2^(130-127) => 1 * 1.1111 * 2^3 => 1 * 1111.1(幾次冪,小數(shù)點(diǎn)就向右移動幾位) => 1 * (2^3 + 2^2 + 2^1 + 2^0 + 2^-1) => 1 * (8 + 4 + 2 + 1 + 0.5) => 15.5

浮點(diǎn)數(shù)與整數(shù)相互賦值

當(dāng)我們在把浮點(diǎn)數(shù)與整數(shù)相互賦值的時候,并不會直接拷貝bit位,而是重新計算出在新的類型中的位模式。
例如:

int i = 5; // 內(nèi)存表示 00000000 | 00000000 | 00000000 | 00000101
// 重新計算5在float中的表示方式
float f = i; // 內(nèi)存表示 0 | 00000000 | 00000000000000000000101
printf("%f", f) // output is 5.0

來一點(diǎn)更刺激的?。?!

// 2^30
int i = 1073741824; // 內(nèi)存表示 01000000 | 00000000 | 00000000 | 00000000
// 這里就不會重新計算在float中的表示方式了,而是直接把bit位拷貝過去。用float的解析方式去解析int的那塊內(nèi)存。
float f = *(float *)&i; // 內(nèi)存表示 0 | 10000000 |00000000000000000000000

// 1 * 2^(128-127) * 1 = 2
printf("%f", f) // output is 2.0

這里就不會重新計算1073741824在float中的表示方式了,而是直接把intbit位拷貝過去。用float的解析方式去解析int的那塊內(nèi)存。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容