Rất có thể bạn không cần phải làm bất kỳ phép toán nào trong công việc hàng ngày của mình. Các toán tử bitwise AND và OR (&và |) của Ruby có lẽ được sử dụng một cách tình cờ hơn là cố ý. Ai đã không vô tình gõ &khi họ có nghĩa là &&?
Nhưng nếu bạn lớn lên trong việc lập trình các ngôn ngữ cấp thấp hơn như C hoặc Assembler, hoặc trong trường hợp của tôi là Turbo Pascal thì có lẽ bạn đã thực hiện ít nhất một số thao tác xoay vòng.
Giải pháp bitwise cho các vấn đề thật tuyệt. Nếu bạn có thể giải quyết một vấn đề của mình bằng cách sử dụng các phép toán cơ bản nhất mà một máy tính có thể thực hiện được (toán học nhị phân), thì điều đó sẽ không dễ dàng hơn thế.
Làm việc với Binary trong Ruby
Bạn có thể biết rằng mọi thứ trong máy tính của bạn được biểu diễn dưới dạng số và những số đó ở định dạng nhị phân. Nhưng điều đó trông như thế nào trong ruby? Trong ví dụ này, tôi đang sử dụng Ruby để tra cứu mã ký tự ASCII cho ký tự "a", sau đó in nó dưới dạng "nhị phân".
Bạn có thể sử dụng ord
trong Ruby để lấy mã ASCII của một ký tự, rồi sử dụng printf
để in biểu diễn "nhị phân" của nó.
Mặc dù chúng ta phải sử dụng một phương thức như printf để hiển thị số dưới dạng nhị phân, nhưng nó luôn là nhị phân. Bạn có thể viết các số nhị phân bên trong mã Ruby của mình bằng cách sử dụng cú pháp như 0b11111111
.
Để thêm các ký tự nhị phân vào mã của bạn, hãy thêm tiền tố chuỗi số một và số 0 bằng 0b. Vì vậy, 0b11111111
Thao tác Giá trị Nhị phân
Bây giờ chúng ta đã biết cách sử dụng các ký tự nhị phân trong ruby, chúng ta có thể bắt đầu chơi với chúng. Để làm điều đó, chúng tôi sẽ sử dụng các toán tử bitwise.
Bạn có thể cảm thấy thoải mái với các toán tử boolean như &&. Biểu thức a &&b chỉ trả về true nếu a và b đều đúng. Các toán tử bitwise rất giống nhau.
Ví dụ, bitwise AND nhận hai giá trị và so sánh chúng từng chút một. Nếu cả hai bit là 1, nó đặt bit đầu ra tương ứng là 1. Nếu không, nó đặt nó thành 0. Vì vậy, nếu bạn có tám bit, bạn sẽ có tám AND riêng biệt xảy ra. Nếu bạn có một bit, bạn có một AND. Các ví dụ sau minh họa điều này bằng cách sử dụng một bit duy nhất.
Ví dụ về các phép toán theo bitwise AND với một bit.
Nó hoạt động giống nhau cho hai bit.
Mỗi cặp bit được đánh dấu riêng biệt
Các toán tử Bitwise của Ruby
Khá nhiều ngôn ngữ lập trình đi kèm với tập hợp các toán tử bitwise này. Nếu bạn không quen thuộc với chúng, hãy dành một ít thời gian đến IRB để thử chúng. Sau khi học chúng, bạn sẽ có thể sử dụng chúng trong suốt phần đời còn lại của mình.
& | Bitwise AND Operator đặt bit của kết quả thành 1 nếu nó là 1 trong cả hai giá trị đầu vào | 0b1010 & 0b0111 == 0b0010 |
| | Bitwise HOẶC Toán tử đặt bit của kết quả thành 1 nếu nó là 1 trong một trong hai giá trị đầu vào | 0b1010 | 0b0111 == 0b1111 |
^ | Toán tử XOR Bitwise đặt bit kết quả thành 1 nếu nó là 1 trong một trong hai giá trị đầu vào, nhưng không phải cả hai | 0b1010 | 0b0111== 0b1101 |
~ | Toán tử nghịch đảo bit đặt bit kết quả thành 0 nếu đầu vào là 1 và ngược lại | ~0b1010 == 0b0101 |
<< | Toán tử Shift Left Bitwise . Di chuyển các bit đầu vào sang trái một số vị trí được chỉ định. | 0b1010 << 4== 0b10100000 |
>> | Toán tử Shift Phải theo Bitwise . Di chuyển các bit đầu vào sang phải một số vị trí nhất định | 0b1010 >> 4 == 0b0000 |
Sử dụng Thực tế:Cờ cấu hình
Được rồi, đây phải là cách sử dụng toán học bitwise nhàm chán nhất. Nhưng nó cũng là một trong những điều phổ biến nhất. Nếu bạn từng phải giao tiếp với mã được viết bằng Java hoặc C hoặc C ++, cuối cùng bạn sẽ bắt gặp các cờ cấu hình bitwise.
Hãy tưởng tượng rằng đó là năm 1996 và bạn vừa xây dựng một hệ thống cơ sở dữ liệu từ đầu. Vì bạn vừa mới xem bộ phim Hackers, nên bạn thấy có thể tốt khi xây dựng một loại kiểm soát truy cập nào đó.
Có 8 hành động mà người dùng có thể thực hiện trong DB của bạn:đọc, ghi, xóa và năm hành động khác. Bạn muốn có thể thiết lập từng người trong số họ một cách độc lập. Bạn có thể có một người dùng có thể đọc nhưng không thể ghi hoặc xóa. Bạn có thể có một cái có thể viết nhưng không làm rớt bảng.
Cách hiệu quả nhất để lưu trữ các cờ cấu hình này là các bit trong một byte đơn. Sau đó, người dùng sẽ chỉ cần HOẶC họ với nhau để tạo bất kỳ tổ hợp quyền nào họ cần.
MYDB_READ = 0b00000001 # These numbers are called bitmasks
MYDB_WRITE = 0b00000010
MYDB_DELETE = 0b00000100
MYDB_INDEX = 0b00001000
user.permissions = MYDB_READ | MYDB_WRITE
Nhân tiện, điều này thực sự tương tự như cách xử lý quyền đối với tệp unix. Nếu bạn đã từng tự hỏi tại sao bạn phải sử dụng một con số kỳ diệu chỉ để tạo một tệp ở chế độ chỉ đọc, thì giờ bạn đã biết.
Sẽ không hữu ích lắm khi lưu trữ các tùy chọn cấu hình theo từng bit trừ khi bạn có thể phát hiện xem một bit nào đó đã được đặt hay chưa. Để làm điều đó, bạn chỉ cần sử dụng một chút AND.
Sử dụng AND với mặt nạ bit để xác định xem một bit có được đặt hay không.
Ít Sử dụng Thực tế hơn (Trừ khi bạn là lập trình viên C)
Bây giờ chúng ta hãy làm một số điều kỳ diệu toán học.
Trong lịch sử, thao tác bit được sử dụng khi bạn cố gắng rút ngắn phần mili giây cuối cùng của một số phép tính. Vì vậy, như bạn có thể tưởng tượng, nó đã được sử dụng rất nhiều bởi các lập trình viên đồ họa và những người khác, những người cần hiệu suất cao hơn tất cả.
Vì vậy, các kỹ thuật như thế này không thực tế cho việc phát triển Ruby hàng ngày. Nhưng chúng vẫn là một bài tập học tập thú vị và có thể hữu ích một cách hợp pháp nếu bạn tham gia vào những thứ như lập trình hệ thống nhúng. Để có cách xử lý triệt để hơn nhiều, hãy xem danh sách các bản hack bit chập chờn này.
Nhân &Chia theo quyền hạn của hai
Hãy xem các biểu diễn nhị phân của các số 1, 2, 4 và 8. Như bạn có thể thấy, nhân đôi số tương đương với việc dịch chuyển tất cả các bit sang trái một chỗ. Tương tự, giảm một nửa số cũng giống như chuyển sang phải.
Biểu diễn nhị phân của 1, 2, 4 8
Nếu bạn nhớ lại, chúng tôi có các toán tử shift-left và shift-right. Điều đó có nghĩa là chúng ta có thể nhân và chia lũy thừa của hai đơn giản bằng cách dịch chuyển các bit.
Bạn có thể sử dụng toán tử dịch chuyển bit để nhân hoặc chia cho lũy thừa của hai
Tính trung bình của hai số nguyên dương
Bạn có thể thực hiện phép cộng cơ bản của hai số nguyên như sau:(x + y) ==(x &y) + (x | y) ==(x ^ y) + 2 * (x &y). Để tính giá trị trung bình, tất cả những gì bạn cần là một phép chia nhỏ cho hai mà bạn có thể nhận được thông qua toán tử shift bên phải.
Bạn có thể tính trung bình hai số nguyên dương như vậy:(x&y)+((x^y)>>1)
Căn bậc hai nghịch đảo nhanh
Tôi không thể thực hiện một bài đăng trên blog mà không có một trong những ví dụ huyền thoại nhất. Đó là phép xấp xỉ căn bậc hai nghịch đảo nhanh của John Carmack, từ mã nguồn Arena 3 Quake năm 1999. Thuật toán không phải của anh ấy, nhưng cách triển khai nổi tiếng nhất là.
Tôi sẽ không cố gắng chuyển trực tiếp điều này sang Ruby, vì nó hoạt động bằng cách xây dựng một biểu diễn nhị phân của một số dấu phẩy động theo cách không thể dịch trực tiếp từ C sang Ruby.
float Q_rsqrt( float number )
{
long i;
float x2, y;
const float threehalfs = 1.5F;
x2 = number * 0.5F;
y = number;
i = * ( long * ) &y; // evil floating point bit level hacking
i = 0x5f3759df - ( i >> 1 ); // what the fuck?
y = * ( float * ) &i;
y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
// y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed
return y;
}