Máy tính bitwise
Thực hiện các phép toán bitwise trên các số nguyên và xem kết quả ở dạng thập phân, thập lục phân và nhị phân.
Cách thức hoạt động
- Nhập hai số: nhập các giá trị để thực hiện phép toán, ở dạng thập phân, nhị phân (tiền tố 0b) hoặc thập lục phân (tiền tố 0x).
- Chọn phép toán: chọn AND, OR, XOR, NOT, dịch trái (<<) hoặc dịch phải (>>).
- Xem kết quả: đầu ra hiển thị kết quả ở dạng thập phân, nhị phân và thập lục phân cùng một lúc, với hình ảnh hóa bitwise.
Tại sao sử dụng máy tính bitwise?
Các phép toán bitwise là cơ bản trong lập trình hệ thống, mật mã học, phát triển trò chơi, đồ họa, mạng và hệ thống nhúng. Hiểu cách AND, OR, XOR và các phép dịch xử lý các bit riêng lẻ là rất quan trọng để bật/tắt cờ, đóng gói dữ liệu và triển khai các thuật toán hiệu quả. Máy tính này hiển thị phép toán ở mức bit để xem chính xác cách mỗi bit bị ảnh hưởng.
Tính năng
- Tất cả các toán tử bitwise: AND (&), OR (|), XOR (^), NOT (~), dịch trái (<<) và dịch phải (>>).
- Đầu vào đa cơ số: nhập các số ở dạng thập phân, nhị phân (0b…) hoặc thập lục phân (0x…).
- Đầu ra đa cơ số: các kết quả được hiển thị đồng thời ở dạng thập phân, nhị phân và thập lục phân.
- Hình ảnh hóa các bit: một lưới trực quan hiển thị các bit nào được kích hoạt cho mỗi toán hạng và cho kết quả.
- Chế độ có dấu/không dấu: chuyển đổi giữa các số nguyên 8, 16, 32 và 64 bit.
Câu hỏi thường gặp
XOR dùng để làm gì trong lập trình?
XOR (^) được sử dụng để đảo bit, mã hóa/làm rối đơn giản, hoán đổi các biến mà không cần biến tạm, kiểm tra chẵn lẻ và trộn các hash. Nó trả về 1 khi các bit khác nhau và 0 khi chúng giống nhau.
Sự khác biệt giữa << và >> là gì?
Dịch trái (<<) di chuyển tất cả các bit sang trái, tương đương với phép nhân với lũy thừa của 2. Dịch phải (>>) di chuyển các bit sang phải, tương đương với phép chia cho lũy thừa của 2. Dịch phải số học giữ nguyên bit dấu; dịch phải logic điền vào bằng số 0.
Cách kích hoạt hoặc vô hiệu hóa một bit cụ thể?
Để kích hoạt bit n: value |= (1 << n). Để vô hiệu hóa bit n: value &= ~(1 << n). Để đảo bit n: value ^= (1 << n). Để kiểm tra nếu bit n được kích hoạt: (value & (1 << n)) !== 0.
Từ đại số Boolean đến silicon: làm thế nào các phép toán bit trở thành phổ quát
Các phép toán bit mà mọi CPU hiện đại triển khai đến từ hai bài báo nền tảng được xuất bản cách nhau hơn 80 năm. Vào năm 1854, George Boole đã xuất bản «An Investigation of the Laws of Thought», định nghĩa đại số của logic hai giá trị, AND, OR, NOT và các đẳng thức của chúng. Đó là một công trình triết học, không phải kỹ thuật. Sau đó vào năm 1937, một sinh viên thạc sĩ 21 tuổi của MIT tên là Claude Shannon đã viết luận văn thạc sĩ «A Symbolic Analysis of Relay and Switching Circuits», chứng minh rằng đại số Boolean có thể mô tả các mạch rơ-le điện và do đó bất kỳ phép tính logic nào cũng có thể được triển khai vật lý. Luận văn này được trích dẫn rộng rãi là luận văn thạc sĩ quan trọng nhất của thế kỷ 20 và là nền tảng của tất cả điện tử kỹ thuật số. Bù hai, biểu diễn nhị phân cho các số âm mà mọi CPU sử dụng, đã được cấp bằng sáng chế vào năm 1962 bởi Burroughs Corporation nhưng đã được sử dụng trong IBM 704 (1954) và các máy thời kỳ đầu khác. IEEE 754 đã chuẩn hóa biểu diễn dấu phẩy động vào năm 1985, cố định bố cục bit mà mọi Number JavaScript vẫn sử dụng, 1 bit dấu, 11 số mũ, 52 mantissa cho binary64. Ngày nay, các toán tử ngôn ngữ C & | ^ ~ << >> ánh xạ gần như trực tiếp đến các lệnh CPU đơn lẻ, đó là lý do tại sao mã bit có thể nhanh hơn đáng kể so với các phép tương đương số học.
Sáu toán tử, chúng làm gì ở cấp độ bit
- AND (
&). Bit kết quả là 1 chỉ khi cả hai bit đầu vào đều là 1.0b1100 & 0b1010 = 0b1000. Sử dụng để che, cô lập các bit cụ thể, hoặc kiểm tra xem một cờ có được đặt không:flags & FLAG_READkhác không khi và chỉ khiFLAG_READđang bật. - OR (
|). Bit kết quả là 1 nếu bất kỳ bit đầu vào nào là 1.0b1100 | 0b1010 = 0b1110. Sử dụng để đặt bit, kết hợp cờ:flags = FLAG_READ | FLAG_WRITE. - XOR (
^). Bit kết quả là 1 nếu chính xác một bit đầu vào là 1.0b1100 ^ 0b1010 = 0b0110. Sử dụng để chuyển đổi bit, làm rối đơn giản (x ^ x = 0, vì vậy XOR là nghịch đảo của chính nó), tính chẵn lẻ và trộn hash. - NOT (
~). Lật mọi bit.~0b0000_1111 = 0b1111_0000(trong 8 bit). Trong bù hai,~xbằng-x − 1, vì vậy~5 === -6trong JavaScript. - Dịch trái (
<<). Di chuyển tất cả các bit sang trái n vị trí, lấp đầy bên phải bằng số không.1 << 3 === 8, tương đương với nhân 2³. Hữu ích để xây dựng mặt nạ bit:1 << nlà «bit ở vị trí n». - Dịch phải (
>>số học,>>>logic). Di chuyển bit sang phải. Dịch số học (>>trong JS, Java, C có dấu) bảo toàn bit dấu; dịch logic (>>>trong JS,>>trong C không dấu) lấp đầy bằng số không.-8 >> 1 === -4nhưng-8 >>> 1 === 2147483644trong JavaScript vì>>>ép kiểu thành int không dấu 32-bit trước.
Nơi bitwise thực sự kiếm tiền lương
- Cờ bit và chuyển đổi tính năng. Đóng gói 32 tùy chọn boolean vào một số nguyên duy nhất. Quyền tệp Linux (
chmod 755), tiêu đề giao thức mạng (TCP, IPv4) và cờ trạng thái OpenGL đều sử dụng mẫu này. Một kiểm tra&cho bạn biết liệu một tính năng có được bật hay không. - Quyền tệp Unix.
rwxr-xr-xlà0b111_101_101 = 0o755 = 493. Ba nhóm là người dùng/nhóm/khác, mỗi ba bit là read/write/execute. Máy tính chmod chuyển đổi dạng ký hiệu thành số nguyên mà shell của bạn muốn. - Mặt nạ mạng con IP. Mặt nạ
/24là255.255.255.0hoặc0xFFFFFF00. Để kiểm tra xem một IP có trong mạng con không:(ip & mask) === (network & mask). Được sử dụng bởi mọi bộ định tuyến và tường lửa trên thế giới. - Mật mã học và băm. SHA-256, AES, ChaCha20, BLAKE3, mọi hash và mật mã hiện đại đều được xây dựng chủ yếu từ AND, OR, XOR, NOT và xoay. XOR một mình là cơ sở của các sổ tay một lần, được chứng minh là không thể phá vỡ khi khóa thực sự ngẫu nhiên và được sử dụng một lần.
- Thao tác màu sắc. Một màu RGBA 32-bit đóng gói bốn kênh 8-bit:
(R << 24) | (G << 16) | (B << 8) | A. Để trích xuất kênh đỏ:(color >> 24) & 0xFF. Được sử dụng bởi HTML canvas, OpenGL, mọi định dạng hình ảnh. - Thủ thuật hiệu suất.
x & 1nhanh hơn nhiều so vớix % 2trên hầu hết các CPU (một chu kỳ so với ~10).x >> 1chia cho 2 trong một chu kỳ. Căn chỉnh lũy thừa hai, đếm dân số bit và các thủ thuật xoắn bit có ở khắp mọi nơi trong mã quan trọng về hiệu suất. (Xem trang «Bit Twiddling Hacks» của Sean Anderson để biết một danh mục nổi tiếng.) - Hệ thống nhúng. Bộ vi điều khiển ánh xạ các thanh ghi phần cứng đến các vị trí bit cụ thể trong bộ nhớ. Việc cấu hình một UART hoặc chân GPIO yêu cầu masking và shifting để đọc hoặc ghi các bit chính xác mà không làm xáo trộn phần còn lại. Cú pháp bitfield của C là đường ngọt thuận tiện cho các mẫu này.
Lỗi cắn
- Nhầm lẫn
&với&&và|với||. Dạng đơn là bitwise, dạng kép là logic ngắn mạch.1 & 2 === 0(không có bit chung) nhưng1 && 2 === 2(cả hai đều đúng, trả về thứ hai). Trộn chúng làm hỏng logic điều kiện một cách âm thầm. - Bất ngờ về thứ tự ưu tiên toán tử.
x & FLAG === 0có nghĩa làx & (FLAG === 0), không phải(x & FLAG) === 0, vì===ràng buộc chặt hơn&. Luôn đóng ngoặc các biểu thức bitwise trong điều kiện. - Nhầm lẫn dịch có dấu vs không dấu. Trong JavaScript,
>>là số học (mở rộng dấu),>>>là logic (lấp đầy số không). C phân biệt theo kiểu biến, kiểu có dấu sử dụng số học, không dấu sử dụng logic. Java có cả>>và>>>giống JavaScript. Không khớp với quy ước ngôn ngữ âm thầm đảo ngược ý nghĩa. - JavaScript cắt ngắn đến 32 bit cho các phép toán bit. Mặc dù
Numberlà float 64-bit, tất cả các toán tử bitwise đều ép buộc toán hạng thành int32 trước.0x100000000 & 0xFF === 0, không phải 0 + byte thấp, vì toán hạng bị cắt ngắn xuống 32 bit trước AND. Đối với thao tác bit 64-bit sử dụngBigInt, có các toán tử bitwise riêng. - Dịch bởi > 31 trong JavaScript.
1 << 32 === 1, không phải 0. Lượng dịch chuyển được lấy modulo 32. C thậm chí còn nguy hiểm hơn: dịch chuyển nhiều hơn chiều rộng bit là hành vi không xác định, vì vậy trình biên dịch có thể làm bất cứ điều gì. Luôn kiểm tran < bitWidthtrước khi dịch chuyển theo giá trị thời gian chạy. - Endianness trong mã đóng gói byte.
(r << 24) | (g << 16) | (b << 8) | atạo ra0xRRGGBBAA. Việc byte được lưu trữ theo thứ tự đó hoặc đảo ngược trong bộ nhớ phụ thuộc vào nền tảng. ImageData trong HTML canvas là little-endian trên x86; định dạng tệp PNG là big-endian. Chuyển đổi rõ ràng vớiDataViewkhi đọc các định dạng nhị phân. - Quên bù hai.
~0 === -1, không phải0xFFFFFFFF, vì tất cả các số 1 là mã hóa bù hai của −1. Để có được diễn giải 32-bit không dấu trong JavaScript:(~0) >>> 0 === 4294967295.
Tại sao bù hai, và nó có nghĩa là gì ở cấp độ bit
Mọi CPU hiện đại đại diện cho các số nguyên âm bằng cách sử dụng bù hai. Trong một byte có dấu 8-bit, các giá trị 0 đến 127 được mã hóa dưới dạng nhị phân 0000_0000 đến 0111_1111. Sau đó −1 được mã hóa dưới dạng 1111_1111, −2 dưới dạng 1111_1110, xuống đến −128 dưới dạng 1000_0000. Lý do: với mã hóa này, phép cộng hoạt động theo cùng một cách cho dù đầu vào là có dấu hay không dấu, CPU không cần các lệnh add-có-dấu và add-không-dấu riêng biệt. Tính bất đối xứng là phạm vi âm lớn hơn phạm vi dương một (trong 8 bit, −128 đến +127), đó là lý do tại sao Math.abs(INT_MIN) tràn trong mọi ngôn ngữ có số nguyên chiều rộng cố định. Các mã hóa cũ hơn dấu-độ lớn (một bit cho dấu, phần còn lại cho độ lớn) và bù một tồn tại trong những năm 1950-60 nhưng thua bù hai vì chúng có hai cách biểu diễn số không và yêu cầu phần cứng trường hợp đặc biệt cho phép phủ định.
Các câu hỏi thường gặp khác
Tại sao ~5 bằng -6 thay vì 250?
Bởi vì trong bù hai (mã hóa mà mọi CPU hiện đại sử dụng), lật mọi bit của một số dương cho bạn -n - 1. Vì vậy ~5 === -6 và ~0 === -1. Trong ngữ cảnh không dấu 8-bit, mẫu bit tương tự ~5 (nhị phân 1111_1010) sẽ đại diện cho 250. JavaScript coi kết quả là có dấu 32-bit, vì vậy bạn thấy -6. Để có được diễn giải không dấu: (~5) >>> 0 trong 32-bit, cho 4294967290, hoặc mask xuống 8 bit với ~5 & 0xFF cho 250.
XOR có thực sự là một mã hóa không?
XOR là khối xây dựng của mọi mật mã đối xứng hiện đại, nhưng XOR một mình không phải là mã hóa an toàn. Một sổ tay một lần, XOR với một khóa thực sự ngẫu nhiên dài bằng thông điệp, được sử dụng đúng một lần, là không thể phá vỡ về mặt thông tin lý thuyết (Shannon, 1949). Sử dụng lại khóa, hoặc sử dụng khóa ngắn hơn thông điệp, và phân tích tần số phá vỡ nó một cách tầm thường. Các mật mã thực sự như AES sử dụng XOR cộng với khuếch tán và thay thế để khuếch đại một khóa ngắn thành một luồng byte giả ngẫu nhiên trông giống như một sổ tay một lần đối với bất kỳ ai không có khóa. Vì vậy «mã hóa với XOR» chỉ tốt cho việc làm rối tầm thường, không bao giờ cho các bí mật thực sự.
Khi nào tôi nên sử dụng BigInt thay vì Number để thao tác bit?
Bất cứ khi nào bạn cần độ chính xác bitwise nhiều hơn 32 bit. Các toán tử bitwise JavaScript cắt ngắn các toán hạng Number thành số nguyên có dấu 32-bit trước khi tính toán. Nếu bạn cần mặt nạ 64-bit (ví dụ: thao tác một bộ cờ tính năng 64-bit, làm việc với offset mmap Linux, hoặc triển khai SHA-512), hãy sử dụng BigInt: 0xFFFFFFFFFFFFFFFFn & 0xFFn === 255n. BigInt chậm hơn Number, ~3-10× tùy thuộc vào hoạt động, vì vậy hãy dành nó cho các trường hợp 32 bit thực sự quá ít.
Làm thế nào để đếm số bit 1 trong một số?
Đây là đếm dân số hoặc trọng số Hamming. Hầu hết các CPU hiện đại đều có một lệnh duy nhất cho điều đó (POPCNT trên x86, VCNT trên ARM). Trong JavaScript, không có sẵn, vì vậy hãy sử dụng kinh điển bit-twiddling: let c = 0; while (x) { c += x & 1; x >>>= 1; }. Hoặc thủ thuật SWAR song song từ Hacker's Delight: x = x - ((x >> 1) & 0x55555555); x = (x & 0x33333333) + ((x >> 2) & 0x33333333); x = (x + (x >> 4)) & 0x0F0F0F0F; return (x * 0x01010101) >>> 24;, đếm các bit trong một số nguyên 32-bit trong khoảng một tá chu kỳ.
Dữ liệu của tôi có được gửi đi đâu khi tôi sử dụng máy tính này không?
Không. Mọi hoạt động chạy trong động cơ JavaScript của trình duyệt của bạn, không có cuộc gọi mạng nào xảy ra trong khi tính toán. Mở tab Mạng trong DevTools và nhấp Tính, bạn sẽ thấy không có yêu cầu đi ra. An toàn cho các mask nhạy cảm, khóa hoặc công việc bố cục bit độc quyền.