一、JS進制
// 二進制(Binary system)
// 以0b或0B開頭
var x = 0b10000000000000000000000000000000; // 2147483648
var y = 0B00000000011111111111111111111111; // 8388607
// 二進制轉換
// 正數:就是正數的原碼
// 負數:負號+正數的原碼
// 不是數值的二進制補碼 源碼
(10).toString(2) // 1010
(-10).toString(2) // -1010
// 八進制(Octal number system)
// 以0開頭,ECMAScript 6支持0o
var n = 0755; // 493
var e = 0o755; // 493 ECMAScript 6規範
// 十進制(Decimal system)
// 以0開頭,但是後面跟8以下會當作八進制處理
var l = 0888; // 888 十進制
var o = 0777; // 511 八進制
// 十六進制(Hexadecimal)
// 以0x或0X開頭
0x123456789ABCDEF // 81985529216486900
0XA // 10
二、原碼、反碼、補碼
先讓我們看下 1 和 -1 原碼、反碼、補碼
然後我們通過這2個數字來解釋原碼、反碼、補碼
-
原碼:數字的二進制表示
- 有符號數:最高位作為符號位, 0表示+,1表示-
- 無符號數:即無符號位
-
反碼:
- 正數和+0 其反碼就是原碼本身
- 負數和-0 原碼基礎上,符號位保持不變,其餘位數逐位取反,1換成0,0換成1
-
補碼:
- 正數和+0 其補碼就是原碼本身
- 負數和-0 先計算其反碼,然後反碼加上1得到補碼
重點:
- JavaScript 負數顯示 是 負號+原碼(理論上方便查看),比如 parseInt(-10).toString(2) 二進制展示輸出是 -1010
- 數據在內存中是以補碼形式存儲(方便換算),原碼和補碼是在運行過程進行轉換的。二進制的元素其實是補碼的運算 通過補碼計算得到補碼,然後轉成反碼,再轉成原碼(這裏不是減 1 還是加 1 )。
-
補碼的符號位是真實的數值,只是因為補碼的最高位剛好和原碼的符號位相同,所以可以當做符號位看,補碼是為了表示負數而出現的
[S2+S1]補=[S2]補+[S1]補 [S2-S1]補=[S2]補+[-S1]補
舉例計算
-2 的原碼:10000010
-2 的反碼:11111101
-2 的補碼:11111110
計算-2 + (-2),利用補碼計算, 最高位的進位捨棄就好
11111110
+ 11111110
= 11111100 // 補碼
- 1
= 11111011 // 反碼
10000100 = -4 // 原碼
溢出
var uint8 = new Uint8Array(1);
uint8[0] = 256;
console.log(uint8[0]) // 0
Uint8Array是無符號8位視圖, 範圍 0~255, 最大 1111 1111 256是1 0000 0000,因此只能放後8位,所以是0
uint8[0] = -1;
console.log(uint8[0]) // 255
-1在計算機中使用補碼存儲,的補碼是 11111111,按照無符號位那就是 255
正向溢出和負向溢出
上面栗子,第一個是正向溢出,第二個是負向溢出
正向溢出:最小值 + 餘數 - 1
負向溢出:最大值 - 餘數 + 1
var int8 = new Int8Array(1); // -128~127
int8[0] = 128;
console.log(int8[0]) // -128 = -128+128%127-1
int8[0]=-129;
console.log(int8[0]) // 127 = 127-(-129%-128)+1
解決溢出錯誤
int8c = new Uint8ClampedArray(1) // 處理溢出按邊界值
int8c[0]=256
console.log(int8c[0]) // 255
int8c[0]=-1
console.log(int8c[0]) // 0
註解:為什麼是-128~127
三、關於文本字符編碼
1. ASCII 碼
上個世紀60年代,美國製定了一套字符編碼,對英語字符與二進制位之間的關係,做了統一規定。這被稱為 ASCII 碼,一直沿用至今。
ASCII 碼一共規定了128個字符的編碼,比如空格SPACE是32(二進制00100000),大寫的字母A是65(二進制01000001)。這128個符號(包括32個不能打印出來的控制符號),只佔用了一個字節的後面7位,最前面的一位統一規定為0
2. 非 ASCII 編碼
英語用128個符號編碼就夠了,但是用來表示其他語言,128個符號是不夠的。於是不同的通過又根據自己的語言拓展了編碼
但是,這裏又出現了新的問題。不同的國家有不同的字母,同一個數字代表的字符可能不一樣,比如,130在法語編碼中代表了é,在希伯來語編碼中卻代表了字母Gimel (ג)
3. Unicode
因為世界上存在着多種編碼方式,同一個二進制數字可以被解釋成不同的符號。因此,要想打開一個文本文件,就必須知道它的編碼方式,否則用錯誤的編碼方式解讀,就會出現亂碼。
如果有一種編碼,將所有符號都納入其中。那每一個符號都有獨一無二的編碼,那麼亂碼問題就會消失。這就是 Unicode
Unicode 是一個很大的集合,可以容納100多萬個符號。每個符號的編碼都不一樣。比如,U+0639表示阿拉伯字母Ain,U+0041表示英語的大寫字母A,U+4E25表示漢字嚴。具體的符號對應表,可以查詢unicode.org,或者專門的漢字對應表。
4. Unicode 的問題
Unicode 它只規定了符號的二進制代碼,卻沒有規定這個二進制代碼應該如何存儲。
- 如何才能區別 Unicode 和 ASCII ?計算機怎麼知道三個字節表示一個符號,而不是分別表示三個符號
- 英文字母只用一個字節表示就夠了,如果 Unicode 統一規定,每個符號用三個或四個字節表示,那麼每個英文字母前都必然有二到三個字節是0,這對於存儲來説是極大的浪費
5. UTF-8
互聯網的普及,強烈要求出現一種統一的編碼方式。UTF-8 就是在互聯網上使用最廣的一種 Unicode 的實現方式。
其他實現方式還包括 UTF-16(字符用兩個字節或四個字節表示)和 UTF-32(字符用四個字節表示),不過在互聯網上基本不用。
重複一遍,這裏的關係是,UTF-8 是 Unicode 的實現方式之一。
UTF-8 最大的一個特點,就是它是一種變長的編碼方式。它可以使用1~4個字節表示一個符號,根據不同的符號而變化字節長度,具體規則如下:
- 對於單字節的符號,字節的第一位設為
0,後面7位為這個符號的 Unicode 碼。因此對於英語字母,UTF-8 編碼和 ASCII 碼是相同的。 - 對於
n字節的符號(n > 1),第一個字節的前n位都設為1,第n + 1位設為0,後面字節的前兩位一律設為10。剩下的沒有提及的二進制位,全部為這個符號的 Unicode 碼。
下表總結了編碼規則,字母x表示可用編碼的位。
跟據上表,解讀 UTF-8 編碼非常簡單。如果一個字節的第一位是0,則這個字節單獨就是一個字符;如果第一位是1,則連續有多少個1,就表示當前字符佔用多少個字節。
舉例:還是以漢字嚴為例,演示如何實現 UTF-8 編碼。
嚴的 Unicode 是4E25(100111000100101),根據上表,可以發現4E25處在第三行的範圍內(0000 0800 - 0000 FFFF),因此嚴的 UTF-8 編碼需要三個字節,即格式是1110xxxx 10xxxxxx 10xxxxxx。然後,從嚴的最後一個二進制位開始,依次從後向前填入格式中的x,多出的位補0。這樣就得到了,嚴的 UTF-8 編碼是11100100 10111000 10100101,轉換成十六進制就是E4B8A5。
6. 字節序 Little endian 和 Big endian
以漢字嚴為例,Unicode 碼是4E25,需要用兩個字節存儲,一個字節是4E,另一個字節是25。存儲的時候,4E在前,25在後,這就是 Big endian 方式;25在前,4E在後,這是 Little endian 方式。
第一個字節在前,就是"大頭方式"(Big endian),第二個字節在前就是"小頭方式"(Little endian)。
那麼很自然的,計算機怎麼知道某一個文件到底採用哪一種方式編碼?
Unicode 規範定義,每一個文件的最前面分別加入一個表示編碼順序的字符,這個字符的名字叫做"零寬度非換行空格"(zero width no-break space),用FEFF表示。這正好是兩個字節,而且FF比FE大1。
如果一個文本文件的頭兩個字節是FE FF,就表示該文件採用大頭方式;如果頭兩個字節是FF FE,就表示該文件採用小頭方式
舉例
打開"記事本"程序notepad.exe,新建一個文本文件,內容就是一個嚴字,依次採用ANSI,Unicode,Unicode big endian和UTF-8編碼方式保存。
然後,用文本編輯軟件UltraEdit 中的"十六進制功能",觀察該文件的內部編碼方式。
- ANSI:文件的編碼就是兩個字節
D1 CF,這正是嚴的 GB2312 編碼,這也暗示 GB2312 是採用大頭方式存儲的。 - Unicode:編碼是四個字節
FF FE 25 4E,其中FF FE表明是小頭方式存儲,真正的編碼是4E25。 - Unicode big endian:編碼是四個字節
FE FF 4E 25,其中FE FF表明是大頭方式存儲。 - UTF-8:編碼是六個字節
EF BB BF E4 B8 A5,前三個字節EF BB BF表示這是UTF-8編碼,後三個E4B8A5就是嚴的具體編碼,它的存儲順序與編碼順序是一致的。
注意:UTF-8 編碼不存在字節序大小端問題(因為字節序隻影響同時處理多於兩個字節的編碼方式,比如 UTF-16/UTF-32,而UTF-8是按照單字節進行處理的),所以 UTF-8 的 BOM 僅起標註文件編碼方式的作用,可加可不加
7. 關於URL轉碼
網頁的 URL 只能包含合法的字符。合法字符分成兩類。
- URL 元字符:分號(
;),逗號(,),斜槓(/),問號(?),冒號(:),at(@),&,等號(=),加號(+),美元符號($),井號(#) - 語義字符:
a-z,A-Z,0-9,連詞號(-),下劃線(_),點(.),感嘆號(!),波浪線(~),星號(*),單引號('),圓括號(())
除了以上字符,其他字符出現在 URL 之中都必須轉義,規則是根據操作系統的默認編碼,將每個字節轉為百分號(%)加上兩個大寫的十六進制字母。
比如,UTF-8 的操作系統上,https://www.baidu.com/s?ie=UTF-8&wd=中國這個 URL 之中,漢字“中國”不是 URL 的合法字符,所以被瀏覽器自動轉成https://www.baidu.com/s?ie=UTF-8&wd=%E4%B8%AD%E5%9B%BD。其中,“中”轉成了%E4%B8%AD,“國”轉成了%E5%9B%BD。這是因為“中”和“國”的 UTF-8 編碼分別是E4 B8 AD和E5 9B BD,將每個字節前面加上百分號,就構成了 URL 編碼。
8. encodeURI和encodeURIComponent
- encodeURI()方法用於轉碼整個 URL。它的參數是一個字符串,代表整個 URL。它會將元字符和語義字符之外的字符,都進行轉義。
- encodeURIComponent()方法用於轉碼 URL 的組成部分,會轉碼除了語義字符之外的所有字符,即元字符也會被轉碼。所以,它不能用於轉碼整個 URL。它接受一個參數,就是 URL 的片段。
9. js進制轉換
console.log('0'.charCodeAt()) // "48" 十進制
console.log('0'.charCodeAt().toString(16)) // "30" 十六進制
console.log(0x0030.toString(10)) // "48" 十進制
console.log(String.fromCharCode(48)) // "0"
console.log('萬'.charCodeAt().toString(16)) // "4e07" 十六進制
console.log(String.fromCharCode(0x4e07)) // "萬"
console.log('萬'.charCodeAt().toString(2)) // "100111000000111" 二進制
console.log(String.fromCharCode(0b100111000000111)) // "萬"
四、位運算
需要注意:負數按補碼形式參加按位與運算。
1. 與操作符(&)
按位與操作符(&)會對參加運算的兩個數據按二進制位進行與運算,即兩位同時為 1 時,結果才為1,否則結果為0。運算規則如下:
0 & 0 = 0
0 & 1 = 0
1 & 0 = 0
1 & 1 = 1
例如,3 & 5 的運算結果如下:
0000 0011
0000 0101
= 0000 0001
因此 3 & 5 的值為 1。
例如 3 & -5
- 3的二進制 是
0000 0011 - 5的二進制 是
0000 0101 - -5的二進制需要用5的補碼錶示,也就是
1111 1011
與運算
0000 0011
1111 1011
= 0000 0011 = 3
用途:
(1)判斷奇偶
只要根據最未位是0還是1來決定,為0就是偶數,為1就是奇數。因此可以用if ((i & 1) === 0)代替if (i % 2 === 0)來判斷a是不是偶數。
(2)清零
如果想將一個單元清零,即使其全部二進制位為0,只要與一個各位都為零的數值相與,結果為零。
(3)是否2的n次冪
// (x & x - 1) === 0
console.log((2 & 2 - 1) === 0) // true
(4)求平均值防止溢出
// 求平均值,防溢出
function avg(x, y){
return (x & y) + ((x ^ y) >> 1);
}
2. 按位或|
| 運算符跟 & 的區別在於如果對應的位中任一個操作數為1 那麼結果就是1。
// 1的二進制表示為: 00000000 00000000 00000000 00000001
// 3的二進制表示為: 00000000 00000000 00000000 00000011
// -----------------------------
// 1 | 3的二進制表示為: 00000000 00000000 00000000 00000011
console.log(1 | 3) // 3
取整
1.3 | 0 // 1
-1.9 | 0 // -1
3. 按位異或^
^ 如果對應兩個操作位有且僅有一個1時結果為1,其他都是0。
// 1的二進制表示為: 00000000 00000000 00000000 00000001
// 3的二進制表示為: 00000000 00000000 00000000 00000011
// -----------------------------
// 2的二進制表示為: 00000000 00000000 00000000 00000010
console.log(1 ^ 3) // 2
異或運算具有以下性質:
- 交換律:
(a^b)^c == a^(b^c) - 結合律:
(a + b)^c == a^b + b^c - 對於任何數x,都有
x^x=0,x^0=x -
自反性:
a^b^b=a^0=a;// 判斷賦值 if(x === a){ x = b }else{ x =a } // 等價於下面 x = a ^ b ^ x4. 按位非
~
~ 運算符是對位求反,1變0, 0變1,也就是求二進制的反碼。
// 1的二進制表示為: 00000000 00000000 00000000 00000001
// 3的二進制表示為: 00000000 00000000 00000000 00000011
// -----------------------------
// 1反碼二進制表示: 11111111 11111111 11111111 11111110
// 由於第一位(符號位)是1,所以這個數是一個負數。JavaScript 內部採用補碼形式表示負數,即需要將這個數減去1,再取一次反,然後加上負號,才能得到這個負數對應的10進制值。
// -----------------------------
// 1的反碼減1: 11111111 11111111 11111111 11111101
// 反碼取反: 00000000 00000000 00000000 00000010
// 表示為10進制加負號:-2
console.log(~ 1) // -2
- 簡單記憶:一個數與自身的取反值相加等於-1。
5. 左移<<
<<左移指定次數,其移動規則:丟棄高位,低位補0
// 1的二進制表示為: 00000000 00000000 00000000 00000001
// -----------------------------
// 2的二進制表示為: 00000000 00000000 00000000 00000010
console.log(1 << 1) // 2
6. 有符號右移>>
>>向右移動指定的位數。向右被移出的位被丟棄,拷貝最左側的位以填充左側。由於新的最左側的位總是和以前相同,符號位沒有被改變。所以被稱作“符號傳播”。
// 1的二進制表示為: 00000000 00000000 00000000 00000001
// -----------------------------
// 0的二進制表示為: 00000000 00000000 00000000 00000000
console.log(1 >> 1) // 0
// -1 >>> 1
// -1的補碼錶示為: 11111111111111111111111111111111
// 右移後,還是: 11111111111111111111111111111111
console.log(-1 >> 1) // -1
7. 無符號右移>>>
>>>右移動指定的位數。向右被移出的位被丟棄,左側用0填充。因為符號位變成了0,所以結果總是非負的。(譯註:即便右移 0 個比特,結果也是非負的。)
對於非負數,有符號右移和無符號右移總是返回相同的結果。例如, 9 >>> 2 得到 2 和 9 >> 2 相同。
TODO
阮一峯Base64筆記
浮點型數字的存儲和計算
0.1 + 0.2不等於0.3?為什麼JavaScript有這種“騷”操作?
參考鏈接
阮一峯字符編碼筆記:ASCII,Unicode 和 UTF-8
URL 編碼與解碼使用詳解
補碼的符號位為什麼能參與運算
原碼運算、反碼運算、補碼運算和溢出
為什麼8位有符號類型的數值範圍是-128~127
web 開發之字符、編碼與二進制(一)
JavaScript裏面的二進制