Toán tử bit

Toán tử dịch bit xem các toán hạng của nó như là một chuỗi nhị phân 32 bit (gồm các số 0 và 1) thay vì là các số thập phân, bát phân hoặc thập lục phân. Ví dụ: số thập phân 9 được biểu diễn nhị phân là 1001, toán tử dịch bit sẽ thực hiện các phép toán trên biểu diễn nhị phân này, nhưng kết quả trả về là một giá trị số chuẩn của JavaScript.

  • Tất cả các số trong JavaScript được lưu trữ dưới dạng số dấu phẩy động 64 bit với gốc là 10. JavaScript không phải là một ngôn ngữ kiểu, khác với nhiều ngôn ngữ lập trình khác, JavaScript không xác định các loại số khác nhau như số nguyên, số ngắn, số dài, số dấu phẩy động, v.v.
  • Độ chính xác của số nguyên (không sử dụng dấu chấm hoặc ký hiệu mũ) là tối đa 15 chữ số. Độ chính xác của số dấu phẩy động là tối đa 17 chữ số, nhưng phép tính dấu phẩy động không luôn chính xác 100%.
  • Toán tử dịch bit thực hiện các phép toán trực tiếp trên các bit nhị phân của số, nó là một phép toán rất cơ bản, ưu điểm là tốc độ rất nhanh, nhưng nhược điểm là không thể sử dụng trong nhiều trường hợp.
  • Toán tử dịch bit chỉ hoạt động với số nguyên, nếu một toán hạng không phải là số nguyên, nó sẽ tự động chuyển đổi thành số nguyên trước khi thực hiện phép toán.
  • Trong JavaScript, số được lưu trữ dưới dạng số dấu phẩy động 64 bit, nhưng khi thực hiện phép dịch bit, nó được thực hiện trên số nguyên 32 bit có dấu và kết quả trả về cũng là một số nguyên 32 bit có dấu.

Các toán tử dịch bit

JavaScript có tổng cộng 7 toán tử dịch bit.

Toán tử AND bit

Toán tử AND bit (&) kết hợp các bit tương ứng trong các số nhị phân theo một cách đặc biệt, nếu cả hai bit tương ứng đều là 1, thì kết quả là 1, nếu bất kỳ bit nào là 0 thì kết quả là 0.

// Biểu diễn nhị phân của 1 là: 00000000 00000000 00000000 00000001
// Biểu diễn nhị phân của 3 là: 00000000 00000000 00000000 00000011
// -----------------------------
// Biểu diễn nhị phân của 1 là: 00000000 00000000 00000000 00000001
 
console.log(1 & 3);
// 1

Toán tử OR bit

Toán tử OR bit (|) khác với toán tử & ở chỗ nếu bất kỳ bit tương ứng nào trong hai toán hạng là 1 thì kết quả là 1.

// Biểu diễn nhị phân của 1 là: 00000000 00000000 00000000 00000001
// Biểu diễn nhị phân của 3 là: 00000000 00000000 00000000 00000011
// -----------------------------
// Biểu diễn nhị phân của 3 là: 00000000 00000000 00000000 00000011
 
console.log(1 | 3);
// 3

Toán tử XOR bit

Toán tử XOR bit (^) cho kết quả là 1 nếu chỉ có một trong hai bit tương ứng là 1, các trường hợp khác đều là 0.

// Biểu diễn nhị phân của 1 là: 00000000 00000000 00000000 00000001
// Biểu diễn nhị phân của 3 là: 00000000 00000000 00000000 00000011
// -----------------------------
// Biểu diễn nhị phân của 2 là: 00000000 00000000 00000000 00000010
 
console.log(1 ^ 3);
// 2

Toán tử NOT bit

Toán tử NOT bit (~) là phép đảo bit, 1 thành 0, 0 thành 1, tức là lấy phần đảo của biểu diễn nhị phân.

// Biểu diễn nhị phân của 1 là: 00000000 00000000 00000000 00000001
// Biểu diễn nhị phân của 3 là: 00000000 00000000 00000000 00000011
// -----------------------------
// Phần đảo của 1 là:       11111111 11111111 11111111 11111110
// Vì bit đầu tiên (bit dấu) là 1, nên số này là một số âm. JavaScript sử dụng biểu diễn bù 2 để biểu diễn số âm, tức là cần trừ đi 1 từ số này, sau đó lấy đảo một lần nữa, rồi thêm dấu âm để có giá trị thập phân tương ứng với số âm này.
// -----------------------------
// Phần đảo của 1 trừ 1:   11111111 11111111 11111111 11111101
// Đảo của phần đảo:       00000000 00000000 00000000 00000010
// Biểu diễn thập phân với dấu: -2
 
console.log(~1);
// -2

Ghi nhớ đơn giản: Tổng của một số và đảo của nó bằng -1.

Dịch trái

Toán tử dịch trái (<<) dịch tất cả các bit của giá trị chỉ định sang trái một số lần xác định, quy tắc dịch: Loại bỏ bit cao, thêm bit 0 ở dưới, tức là dịch tất cả các số thành hệ nhị phân tương ứng sang trái số lần tương ứng, các bit cao bị loại bỏ (bỏ qua), các bit thấp được điền vào bằng số 0.

// Biểu diễn nhị phân của 1 là: 00000000 00000000 00000000 00000001
// -----------------------------
// Biểu diễn nhị phân của 2 là: 00000000 00000000 00000000 00000010
 
console.log(1 << 1);
// 2

Dịch phải có dấu

Dịch phải có dấu (>>) sẽ dịch các bit của toán hạng chỉ định sang phải một số lần xác định. Các bit bị dịch ra phía bên phải sẽ bị loại bỏ và bit trái nhất sẽ được sao chép để điền vào bên trái. Vì bit trái nhất luôn giống như trước đó, nên bit dấu không thay đổi. Do đó, nó được gọi là truyền dấu.

// Biểu diễn nhị phân của 1 là: 00000000 00000000 00000000 00000001
// -----------------------------
// Biểu diễn nhị phân của 0 là: 00000000 00000000 00000000 00000000
 
console.log(1 >> 1);
// 0

Dịch phải không dấu

Dịch phải không dấu (>>>) sẽ dịch toán hạng đầu tiên sang phải một số lần xác định. Các bit bị dịch ra phía bên phải sẽ bị loại bỏ và được điền vào bên trái bằng số 0. Vì bit dấu trở thành 0, nên kết quả luôn là số không âm. (Chú ý: Ngay cả khi dịch 0 bit, kết quả vẫn là số không âm.)

Tổng kết

Toán tửCú phápMô tả
AND bita & bTrả về 1 nếu cả ab tương ứng là 1, ngược lại trả về 0
OR bita | bTrả về 1 nếu ít nhất một trong ab tương ứng là 1, ngược lại trả về 0
XOR bita ^ bTrả về 1 nếu chỉ có một trong ab tương ứng là 1, ngược lại trả về 0
NOT bit~ aĐảo ngược các bit của toán hạng
Dịch tráia << bDịch tất cả các bit của giá trị a sang trái b lần, các bit mới được điền vào bằng 0
Dịch phảia >> bDịch tất cả các bit của giá trị a sang phải b lần, các bit bị mất đi
Dịch phải 0a >>> bDịch tất cả các bit của giá trị a sang phải b lần, các bit bị mất đi và được điền 0

Thực hành tốt nhất

Lấy phần nguyên

Sử dụng ~>><<>>>| để lấy phần nguyên.

console.log(~~6.83); // 6
console.log(6.83 >> 0); // 6
console.log(6.83 << 0); // 6
console.log(6.83 | 0); // 6
// Không thể lấy phần nguyên của số âm bằng >>>
console.log(6.83 >>> 0); // 6

Hoán đổi giá trị

Sử dụng XOR bit ^ để hoán đổi giá trị.

var a = 5;
var b = 8;
 
a ^= b;
b ^= a;
a ^= b;
 
console.log(a); // 8
console.log(b); // 5

XOR cũng thường được sử dụng trong mã hóa.

Chuyển đổi giá trị thập phân sang nhị phân

var number = 3;
var result = number.toString(2);
 
var result2 = (14).toString(2);
// "1110"

Chuyển đổi giá trị màu

Sử dụng &>>| để chuyển đổi giữa giá trị màu RGB và giá trị màu hex.

/**
 * Chuyển đổi giá trị màu hex sang RGB
 * @param  {String} hex Giá trị màu hex
 * @return {String}     Giá trị màu RGB
 */
function hexToRGB(hex) {
  var hexx = hex.replace('#', '0x');
  var r = hexx >> 16;
  var g = (hexx >> 8) & 0xff;
  var b = hexx & 0xff;
  return `rgb(${r}, ${g}, ${b})`;
}
 
/**
 * Chuyển đổi giá trị màu RGB sang giá trị màu hex
 * @param  {String} rgb Giá trị màu RGB
 * @return {String}     Giá trị màu hex
 */
function RGBToHex(rgb) {
  var rgbArr = rgb.split(/[^\d]+/);
  var color = (rgbArr[1] << 16) | (rgbArr[2] << 8) | rgbArr[3];
  return '#' + color.toString(16);
}
 
// -------------------------------------------------
hexToRGB('#ffffff'); // 'rgb(255,255,255)'
RGBToHex('rgb(255,255,255)'); // '#ffffff'

Kiểm tra dấu

function isPos(n) {
  return n === n >>> 0 ? true : false;
}
isPos(-1); // false
isPos(1); // true

Kiểm tra dấu giống nhau

Thường, chúng ta sử dụng x * y > 0 để so sánh xem hai số có cùng dấu hay không. Nhưng nếu sử dụng XOR bit ^, tốc độ tính toán sẽ nhanh hơn.

console.log(-17 ^ (9 > 0));
// false

Kiểm tra số lẻ hay chẵn

Sử dụng toán tử & để kiểm tra một số là lẻ hay chẵn.

Nếu biểu diễn số n dưới dạng nhị phân, chỉ cần kiểm tra bit nhị phân cuối cùng là 1 hay 0 là được.

// Số chẵn & 1 = 0
// Số lẻ & 1 = 1
console.log(2 & 1); // 0
console.log(3 & 1); // 1

Kiểm tra sự tồn tại của chỉ mục

Đây là một kỹ thuật phổ biến, ví dụ như kiểm tra một số có tồn tại trong một mảng hay không:

// Nếu url chứa dấu ? thì thêm dấu & vào sau, ngược lại thêm dấu ? vào sau
url += ~url.indexOf('?') ? '&' : '?';

Vì: ~-1 === 0

-1 được biểu diễn nhị phân là toàn bit 1, sau khi thực hiện phép NOT bit ~, kết quả sẽ trở thành toàn bit 0. Điều này cho thấy rằng số có toàn bit 1 trong bộ nhớ được biểu diễn như thế nào. Các số khác không có toàn bit 1, vì vậy kết quả của phép NOT bit sẽ không phải là 0. Do đó, chúng ta có thể sử dụng tính chất này để kiểm tra indexOf, giúp mã nguồn trở nên ngắn gọn hơn.