Toán tử bit ECMAScript

Toán tử bit hoạt động ở cấp độ cơ bản của số (tức là 32 số bit biểu diễn số).

Nhắc lại số nguyên

Số nguyên ECMAScript có hai loại, đó là số nguyên có dấu (cho phép sử dụng số dương và số âm) và số nguyên vô dấu (chỉ cho phép sử dụng số dương). Trong ECMAScript, tất cả các số nguyên biểu thức đều là số nguyên có dấu, điều này có nghĩa là gì?

Số nguyên có dấu sử dụng 31 bit để biểu diễn giá trị số của số nguyên, sử dụng bit số 32 để biểu diễn dấu số nguyên, 0 biểu diễn số dương, 1 biểu diễn số âm. Khoảng giá trị từ -2147483648 đến 2147483647.

Có hai cách lưu trữ số nguyên có dấu trong hình thức nhị phân, một cách để lưu trữ số dương và một cách để lưu trữ số âm. Số dương được lưu trữ dưới dạng nhị phân thật, mỗi bit trong 31 bit đầu tiên biểu diễn quyền số của 2, bắt đầu từ bit số 1 (bit 0) biểu diễn số 20,vị số 2 (bit 1) biểu diễn số 21Các số không sử dụng được được lấp đầy bằng 0, tức là không tính đến. Ví dụ, hình dưới đây là cách biểu diễn số 18.

Số nguyên có dấu biểu diễn bằng 32 bit

Biểu diễn số nhị phân của 18 chỉ sử dụng 5 số đầu tiên, chúng là các số bit hợp lệ. Khi chuyển đổi số thành chuỗi nhị phân, bạn có thể nhìn thấy các số bit hợp lệ:

var iNum = 18;
alert(iNum.toString(2));	//Xuất ra "10010"

Mã này chỉ xuất ra "10010", không phải là biểu diễn 32 bit của số 18. Các số khác không quan trọng vì chỉ cần 5 số đầu tiên để xác định giá trị số thập phân này. Như hình dưới đây hiển thị:

Số nguyên 18 biểu diễn bằng 5 bit

Số âm cũng được lưu trữ dưới dạng mã nhị phân, nhưng dưới dạng mã bổ sung nhị phân. Bước để tính mã bổ sung nhị phân của số có ba bước:

  1. Xác định biểu diễn nhị phân của phiên bản không âm của số (ví dụ, để tính mã bổ sung nhị phân của -18, trước tiên phải xác định biểu diễn nhị phân của 18)
  2. Tính toán mã phản hồi, tức là thay thế 0 bằng 1 và 1 bằng 0
  3. Thêm 1 vào mã phản hồi

Để xác định biểu diễn nhị phân của -18, trước tiên phải có biểu diễn nhị phân của 18, như sau:

0000 0000 0000 0000 0000 0000 0001 0010

Tiếp theo, tính toán mã phản hồi nhị phân, như sau:

1111 1111 1111 1111 1111 1111 1110 1101

Cuối cùng, thêm 1 vào mã phản hồi nhị phân, như sau:

1111 1111 1111 1111 1111 1111 1110 1101
                                      1
---------------------------------------
1111 1111 1111 1111 1111 1111 1110 1110

Do đó, biểu diễn nhị phân của -18 là 1111 1111 1111 1111 1111 1111 1110 1110. Lưu ý rằng khi xử lý số nguyên có dấu, nhà phát triển không thể truy cập vào bit 31.

Điều thú vị là khi chuyển đổi số nguyên âm thành chuỗi nhị phân, ECMAScript không hiển thị dưới dạng mã bổ sung nhị phân mà là thêm dấu âm vào mã nhị phân chuẩn của giá trị tuyệt đối. Ví dụ:

var iNum = -18;
alert(iNum.toString(2));	//hiển thị "-10010"

Mã này sẽ hiển thị "-10010", không phải là mã bổ sung nhị phân, để tránh truy cập vào bit 31. Để đơn giản hóa, ECMAScript xử lý số nguyên bằng một cách đơn giản, để nhà phát triển không cần quan tâm đến cách sử dụng của chúng.

Mặt khác, số nguyên vô dấu xử lý bit cuối cùng như một số bit khác. Trong chế độ này, bit 32 không biểu thị dấu của số mà là giá trị 231Do vị trí bit này, số nguyên vô dấu có giá trị từ 0 đến 4294967295. Đối với các số nguyên nhỏ hơn 2147483647, số nguyên vô dấu trông giống như số nguyên có dấu, trong khi các số nguyên lớn hơn 2147483647则需要使用 bit 31 (trong số nguyên có dấu, bit này luôn là 0).

Khi chuyển đổi số nguyên vô dấu thành chuỗi, chỉ trả về các bit hợp lệ.

Lưu ý:Tất cả các số nguyên biểu thức đều được lưu trữ theo mặc định là số nguyên có dấu. Chỉ các toán tử tính toán bit của ECMAScript mới có thể tạo ra số nguyên vô dấu.

Tính toán bit NOT

Tính toán bit NOT được biểu thị bằng dấu không ( ~ ), nó là một trong số ít toán tử liên quan đến toán học nhị phân trong ECMAScript.

Phép toán số vị NOT là quá trình xử lý ba bước:

  1. Chuyển đổi toán tử thành số 32 bit
  2. Chuyển đổi số nhị phân thành mã đảo
  3. Chuyển đổi số nhị phân thành số thập phân

Ví dụ:

var iNum1 = 25;		// 25 bằng 00000000000000000000000000011001
var iNum2 = ~iNum1;	// Chuyển đổi thành 11111111111111111111111111100110
alert(iNum2);		// Xuất ra "-26"

Phép toán số vị NOT thực chất là tính giá trị âm của số, sau đó trừ 1, vì vậy 25 trở thành -26. Cũng có thể đạt được kết quả tương tự bằng cách sử dụng phương pháp sau:

var iNum1 = 25;
var iNum2 = -iNum1 -1;
alert(iNum2);	// Xuất ra "-26"

Phép toán số vị AND

Phép toán số vị AND được biểu thị bằng dấu cộng (&), trực tiếp thực hiện phép toán trên hình thức nhị phân của số. Nó đặt hàng số vị của mỗi số, sau đó sử dụng các quy tắc sau để thực hiện phép AND giữa hai số vị cùng vị trí:

Số bit của số thứ nhất Số bit của số thứ hai Kết quả
1 1 1
1 0 0
0 1 0
0 0 0

Ví dụ, để thực hiện phép AND giữa số 25 và 3, mã như sau:

var iResult = 25 & 3;
alert(iResult);	// Xuất ra "1"

Kết quả của phép AND giữa 25 và 3 là 1. Tại sao? Phân tích như sau:

 25 = 0000 0000 0000 0000 0000 0000 0001 1001
  3 = 0000 0000 0000 0000 0000 0000 0000 0011
---------------------------------------------
AND = 0000 0000 0000 0000 0000 0000 0000 0001

Có thể thấy, trong 25 và 3, chỉ có một số vị (số vị 0) chứa số 1, vì vậy, các số vị khác tạo ra đều là 0, vì vậy kết quả là 1.

Phép toán số vị OR

Phép toán số vị OR được biểu thị bằng ký hiệu (|), cũng trực tiếp thực hiện phép toán trên hình thức nhị phân của số. Trong quá trình tính mỗi số vị, phép toán OR sử dụng các quy tắc sau:

Số bit của số thứ nhất Số bit của số thứ hai Kết quả
1 1 1
1 0 1
0 1 1
0 0 0

Dẫn chứng tiếp theo về phép AND như đã sử dụng, phép OR giữa 25 và 3, mã như sau:

var iResult = 25 | 3;
alert(iResult);	// Xuất ra "27"

Kết quả của phép OR giữa 25 và 3 là 27:

25 = 0000 0000 0000 0000 0000 0000 0001 1001
 3 = 0000 0000 0000 0000 0000 0000 0000 0011
--------------------------------------------
OR = 0000 0000 0000 0000 0000 0000 0001 1011

Có thể thấy, trong hai số, có 4 số vị chứa số 1, các số vị này được truyền sang kết quả. Mã nhị phân 11011 bằng 27.

Toán tử toán học XOR

Toán tử toán học XOR được biểu diễn bằng dấu (^), tự nhiên, cũng thực hiện trực tiếp trên dạng nhị phân. XOR khác với OR, nó chỉ trả về 1 khi chỉ có một số bit chứa số 1. Bảng giá trị như sau:

Số bit của số thứ nhất Số bit của số thứ hai Kết quả
1 1 0
1 0 1
0 1 1
0 0 0

Để thực hiện phép XOR giữa 25 và 3, mã như sau:

var iResult = 25 ^ 3;
alert(iResult);	//Xuất ra "26"

Kết quả của việc thực hiện phép XOR giữa 25 và 3 là 26:

 25 = 0000 0000 0000 0000 0000 0000 0001 1001
  3 = 0000 0000 0000 0000 0000 0000 0000 0011
---------------------------------------------
XOR = 0000 0000 0000 0000 0000 0000 0001 1010

Có thể thấy, trong hai số, có 4 số bit chứa số 1, những số bit này được truyền sang kết quả. Mã nhị phân 11010 bằng 26.

Toán tử di chuyển trái

Toán tử di chuyển trái được biểu diễn bằng hai dấu nhỏ hơn (<<). Nó di chuyển tất cả các số bit của số một cách đồng bộ theo số lượng chỉ định. Ví dụ, khi di chuyển số 2 (bằng với 10 nhị phân) trái 5 vị trí, kết quả là 64 (bằng với 1000000 nhị phân):

var iOld = 2;		//Bằng với 10 nhị phân
var iNew = iOld << 5;	//Bằng với 1000000 nhị phân hoặc 64 hệ thống 10

Lưu ý:Khi di chuyển số bit trái, số sẽ có 5 vị trí trống bên phải. Việc di chuyển trái sử dụng 0 để lấp đầy những vị trí trống này, làm cho kết quả trở thành số 32 bit đầy đủ.

Số 2 thực hiện phép dịch trái

Lưu ý:Việc di chuyển trái giữ lại số bit dấu của số. Ví dụ, nếu di chuyển -2 trái 5 vị trí, kết quả là -64, không phải là 64. "Dấu vẫn được lưu trữ ở số bit 32 không?" Đúng vậy, nhưng điều này được thực hiện ở background của ECMAScript, nhà phát triển không thể truy cập trực tiếp vào số bit 32. Thậm chí khi xuất ra dưới dạng chuỗi nhị phân của số âm, cũng hiển thị dưới dạng dấu âm (ví dụ, -2 sẽ hiển thị -10).

Toán tử di chuyển phải có dấu

Toán tử di chuyển phải có dấu được biểu diễn bằng hai dấu lớn hơn (>>). Nó di chuyển tất cả các số bit của số 32 bit một cách đồng bộ và giữ lại dấu của số (dấu cộng hoặc dấu trừ). Toán tử di chuyển phải có dấu ngược lại với việc di chuyển trái. Ví dụ, khi di chuyển 64 phải 5 vị trí, nó sẽ trở thành 2:

var iOld = 64;		// bằng binary 1000000
var iNew = iOld >> 5;	// bằng binary 10 decimal 2

Similar, sau khi dịch chuyển số bit sẽ tạo ra các vị trí trống. Lần này, các vị trí trống nằm ở bên trái của số, nhưng nằm sau vị trí dấu. ECMAScript sử dụng giá trị của dấu để lấp đầy các vị trí trống này, tạo ra số nguyên hoàn chỉnh, như hình dưới đây:

Số 64 thực hiện phép dịch phải có dấu

Toán tử dịch chuyển phải không dấu

Toán tử dịch chuyển phải không dấu được biểu diễn bằng ba dấu lớn hơn (>>>), nó dịch chuyển tất cả các số bit của số không dấu 32 bit. Đối với số dương, kết quả của toán tử dịch chuyển phải không dấu giống như toán tử dịch chuyển phải sang phải có dấu.

Dưới đây là ví dụ về toán tử dịch chuyển phải sang phải có dấu, dịch chuyển 64 phải 5 bit sẽ trở thành 2:

var iOld = 64;		// bằng binary 1000000
var iNew = iOld >>> 5;	// bằng binary 10 decimal 2

Đối với số âm, tình hình lại khác.

Toán tử dịch chuyển phải không dấu lấp đầy tất cả các vị trí trống bằng số 0. Đối với số dương, điều này giống như toán tử dịch chuyển phải sang phải có dấu,而在负数中则被作为正数来处理。

Do kết quả của toán tử dịch chuyển phải không dấu là một số dương 32 bit, vì vậy toán tử dịch chuyển phải sang phải của số nguyên âm luôn nhận được một số rất lớn. Ví dụ, nếu dịch chuyển -64 phải 5 bit, sẽ nhận được 134217726. Làm thế nào để đạt được kết quả này?

Để thực hiện điều này, cần chuyển đổi số này thành hình thức không dấu tương đương (mặc dù số này本身 vẫn là số nguyên có dấu), có thể lấy được bằng cách sử dụng mã sau:

var iUnsigned64 = -64 >>> 0;

Sau đó, sử dụng phương thức toString() của loại Number để lấy biểu diễn bit thực sự của nó, sử dụng cơ số là 2:

alert(iUnsigned64.toString(2));

Nó sẽ tạo ra 11111111111111111111111111000000, tức là biểu diễn số nguyên có dấu -64 dưới dạng mã bổ sung hai của số nguyên có dấu, nhưng nó bằng số nguyên không dấu 4294967232.

Do đó, cần cẩn thận khi sử dụng toán tử dịch chuyển phải sang phải không dấu.