VBA Nâng cao: NOT, OR, XOR và AND toán tử logic tối quan trọng cho tư duy lập trình (2 người xem)

Liên hệ QC

Người dùng đang xem chủ đề này

  • HeSanbi

    Nam Nhân✨Hiếu Lễ Nghĩa Trí Tín✨
    Tham gia
    24/2/13
    Bài viết
    2,764
    Được thích
    4,370
    Giới tính
    Nam
    Hôm nay tôi chia sẻ đến các bạn một kiến thức, mà hầu như các Lập trình viên sơ cấp, không được giảng rỏ về 4 toán tử NOT, OR, AND XOR căn bản trong mọi ngôn ngữ lập trình.

    NOT, OR, AND XOR không phải trả về giá trị đúng sai như các bạn thường vận dụng. Vì chúng là toán tử, chính xác thì chúng là toán tử tính toán chỉ khác cộng, trừ, nhân, chia, ..., chúng tính toán dựa vào phép toán bitwise.
    Nhưng ít sách vở căn bản nào giải thích rõ cho các bạn biết bản chất của chúng.

    Chính vì vậy mà hôm nay tôi chia sẻ thêm kiến thức tuy căn bản nhưng lại là tiền đề cho những cấu trúc mã Nâng cao sau này. Vì chúng là cơ sở tạo các cấu trúc mã với giải thuật, thuật toán sau rộng hơn trong Lập trình. Vì chúng không chỉ có trong một ngôn ngữ là VBA.


    Mục tiêu của tôi là hướng đến lập trình cấp thấp, để đạt được các mục tiêu lập trình xử lý bộ nhớ, Assembly + thunks.

    Lập trình cơ bản mà những ai biết về VBA chỉ là hạt cát. Điều tôi muốn hướng đến là lập trình sâu hơn vào hệ thống, đi sâu vào các thư viện, và cấu trúc thành phần của chúng.
    Hãy xem những gì mà bạn thấy là mã VBA và giao diện ứng dụng. Nhưng những gì bạn không thấy, không biết, chưa biết là mã nguồn, mã máy, mã binary, system, kernel, nhân linus, unix.

    Chính vì mục tiêu tôi đang đặt ra, nên các phép toán bitwise là khởi nguồn để tôi bắt đầu.


    Để bắt đầu chủ đề các bạn cần xem kiến thức cơ bản về các toán tử logic NOT, OR, AND XOR tại Wikipedia:
    (Tiếng Anh) https://en.wikipedia.org/wiki/Bitwise_operation
    (Tiếng Việt) https://vi.wikipedia.org/wiki/Phép_toán_thao_tác_bit



    Phép toán thao tác bit


    Trong phép toán thao tác bit (bitwise) gồm có: Dịch chuyển bit, xóa bit, và quay bit.
    Các toán tử logic hội tụ đủ các thao tác này nên ta tận dụng như các ví dụ dưới dây.


    Trong ngôn ngữ máy tính, các phép toán trên thao tác bit (tiếng Anh: bitwise operation) là các phép toán được thực hiện trên một hoặc nhiều chuỗi bit hoặc số nhị phân tại cấp độ của từng bit riêng biệt. Các phép toán này được thực hiện nhanh, ưu tiên, được hỗ trợ trực tiếp bởi vi xử lý, và được dùng để điều khiển các giá trị để tính toán.
    Đối với các loại vi xử lý đời cũ, các phép toán trên thao tác bit thường nhanh hơn phép chia đáng kể, đôi khi nhanh hơn phép nhân, và đôi khi nhanh đáng kể hơn phép cộng, trong khi các vi xử lý hiện đại thường thực hiện phép nhân và phép cộng nhanh tương đương các phép toán trên thao tác bit do cấu trúc đường ống lệnh của chúng dài hơn. Cũng nhờ vào các lựa chọn trong thiết kế cấu trúc, các phép toán trên thao tác bit thường sử dụng ít tài nguyên hơn.


    Các toán tử thao tác bit

    Các toán tử thao tác bit (tiếng Anh: bitwise operator) là các toán tử được sử dụng chung với một hoặc hai số nhị phân để tạo ra một phép toán thao tác bit. Hầu hết các toán tử thao tác bit đều là các toán tử một hoặc hai ngôi.
    Trong các giải thích dưới đây, bất kỳ dấu hiện nào của vị trí một bit được tính từ phía bên phải (nhỏ nhất), tiến dần về bên trái. Ví dụ: số nhị phân 0001 (số một trong hệ thập phân) có các số 0 ở mọi vị trí trừ vị trí đầu tiên.


    AND

    Toán tử thao tác bit AND lấy 2 toán hạng nhị phân có chiều dài bằng nhau và thực hiện phép toán lý luận AND trên mỗi cặp bit tương ứng bằng cách nhân chúng lại với nhau. Nhờ đó, nếu cả hai bit ở vị trí được so sánh đều là 1, thì bit hiển thị ở dạng nhị phân sẽ là 1 (1 x 1 = 1); ngược lại thì kết quả sẽ là 0 (1 x 0 = 0). Ví dụ:

    ABA & B
    000
    010
    100
    111

    0101 (số thập phân 5)
    AND 0011 (số thập phân 3)

    = 0001 (số thập phân 1)


    Phép toán này có thể được sử dụng để xác định xem nếu một bit được thiết đặt (1) hoặc trống (0). Ví dụ: Cho trước dãy bit 0011 (số 3 trong hệ thập phân), để xác định xem bit thứ 2 có được thiết đặt hay không, ta sử phép toán thao tác bit AND với một dãy bit có chứa số 1 duy nhất ở bit thứ 2, ví dụ:
    0011 (số thập phân 3)
    AND 0010 (số thập phân 2)

    = 0010 (số thập phân 2)

    Vì kết quả 0010 là khác 0, ta biết là bit thứ 2 trong dãy bit ban đầu đã được thiết đặt. Điều này được gọi là che đậy bit. (Bằng phép loại suy, công dụng của mặt nạ, các phần không nên bị thay thế hoặc các phần không được quan tâm. Trong trường hợp này, các giá trị 0 che đậy cho các bit không được quan tâm).
    Nếu ta lưu trữ kết quả, nó có thể được sử dụng để lưu trữ để xóa các bit được lựa chọn trong một thanh ghi. Cho ví dụ 0110 (số 6 trong hệ thập phân), bit thứ 2 có thể được xóa đi bằng cách sử dụng phép toán thao tác bit AND với dãy có một số 0 duy nhất ở bit thứ 2:

    0110 (số thập phân 6)
    AND 1101 (số thập phân 13)

    = 0100 (số thập phân 4)

    Vì đặc tính này, việc kiểm tra tính chẵn lẻ của số nhị phân trở nên dễ dàng bằng cách kiểm tra giá trị của bit có giá trị thấp nhất. Sử dụng ví dụ phía trên ta có:
    0110 (số thập phân 6)
    AND 0001 (số thập phân 1)

    = 0000 (số thập phân 0)

    Trong C, C++, Java, C#, toán tử thao tác bit AND được biểu diễn bằng ký hiệu "&" (dấu và):
    x = y & z;

    Trong Pascal, toán tử này là "and". Ví dụ:
    x:= y and z;



    NOT

    Toán tử thao tác bit NOT, hay còn gọi là còn được gọi là toán tử lấy phần bù (complement), là toán tử một ngôi thực hiện phủ định luận lý trên từng bit, tạo thành bù 1 (one’s complement) của giá trị nhị phân cho trước. Bit nào là 0 thì sẽ trở thành 1, và 1 sẽ trở thành 0. Ví dụ:
    NOT 0111 (số thập phân 7)

    = 1000 (số thập phân 8)

    Bảng chân trị cho NOT:


    ANOT A
    01
    10


    Phép toán thao tác bit lấy phần bù sẽ tương đương với bù 2 (two’s complement) của giá trị được tính trừ đi 1. Nếu phép toán bù 2 được sử dụng, như vậy:
    NOT x = -x – 1
    Đối với các số nguyên không âm, phép toán thao tác bit lấy phần bù của một số là "hình ảnh phản chiếu" của số đó tính tới điểm giữa của giới hạn số nguyên không âm. Vi dụ: đối với số nguyên 8-bit, NOT x = 255 – x, có thể được biểu diễn trên đồ thị dưới dạng một đường thẳng đi xuống mà đường thẳng đó "lật" một dãy tăng dần từ 0 đến 255, đến một dãy giảm dần từ 255 xuống 0. Một ví dụ đơn giản nhưng dễ hình dung là việc đảo ngược một hình ảnh trắng đen mà mỗi pixel trong đó được coi là một số nguyên không âm.
    Trong các ngôn ngữ lập trình C, C++, Java, C#, toán tử thao tác bit NOT được biểu diễn bằng ký hiệu "~" (dấu ngã). Trong Pascal, toán tử này là "not". Ví dụ:

    x = ~y; // C

    Hay
    x:= not y; { Pascal }

    Câu lệnh trên sẽ gán cho x giá trị "NOT y" - tức phần bù của y. Chú ý rằng, toán tử này không tương đương với toán tử luận lý "not" (biểu diễn bằng dấu chấm than "!" trong C/C++). Về vấn đề này, xin xem ở bài toán tử hoặc các bài về ngôn ngữ C/C++.
    Toán tử NOT hữu dụng khi ta cần tìm bù 1 của một số nhị phân. Nó cũng có thể được sử dụng làm bước đầu tiên để tìm số bù 2.


    OR

    Phép toán trên thao tác bit OR lấy hai dãy bit có độ dài bằng nhau và thực hiện phép toán lý luận bao hàm OR trên mỗi cặp bit tương ứng. Kết quả ở mỗi vị trí sẽ là 0 nếu cả hai bit là 0, ngược lại thì kết quả là 1. Ví dụ:
    0101 (số thập phân 5)
    OR 0011 (số thập phân 3)
    = 0111 (số thập phân 7)

    Bảng chân trị cho OR:


    ABA OR B
    000
    011
    101
    111

    Trong C, C++, Java, C#, toán tử thao tác bit OR được biểu diễn bằng ký hiệu "|" (vạch đứng). Trong Pascal, toán tử này là "or". Ví dụ:
    x = y | z; // C

    Hay:
    x:= y or z; { Pascal }

    Câu lệnh trên sẽ gán cho x kết quả của "y OR z". Chú ý rằng toán tử này không tương đương với toán tử luận lý "or" (biểu diễn bằng cặp vạch đứng "||" trong C/C++). Về vấn đề này, xin xem ở bài toán tử hoặc các bài về ngôn ngữ C/C++.
    Phép toán thao tác bit OR có thể được sử dụng để thiết đặt bit được chọn thành 1. Ví dụ: Nó có thể được sử dụng để bật (set) một bit (hoặc cờ) trong thanh ghi, trong đó mỗi bit đại diện cho một trạng thái trong phép logic đúng sai (boolean). Vì thế, 0010 (số 2 thập phân) có thể được xem là một bộ 4 cờ, trong đó cờ thứ nhất, thứ ba và thứ tư là trống (0) và cờ thứ hai được bật (1). Cờ thứ tư có thể được bật bằng cách thực hiện phép toán thao tác bit OR giữa giá trị này và một dãy bit với duy nhất bộ bit thứ 4:

    0010 (số thập phân 2)
    OR 1000 (số thập phân 8)

    1010 (số thập phân 10)

    Kỹ thuật này là một cách hiệu quả để lưu trữ một số trong những giá trị phép toán logic đúng sai (boolean) sử dụng ít bộ nhớ nhất có thể.
    Khi làm việc với các máy không có nhiều không gian bộ nhớ trống, các lập trình viên thường áp dụng kĩ thuật trên. Lúc đó, thay vì khai báo tám biến kiểu bool (C++) độc lập, người ta sử dụng từng bit riêng lẻ của một byte để biểu diễn giá trị cho tám biến đó.


    XOR

    Phép toán thao tác bit XOR lấy hai dãy bit có cùng độ dài và thực hiện phép toán logic bao hàm XOR trên mỗi cặp bit tương ứng. Kết quả ở mỗi vị trí là 1 chỉ khi bit đầu tiên là 1 hoặc nếu chỉ khi bit thứ hai là 1, nhưng sẽ là 0 nếu cả hai là 0 hoặc cả hai là 1. Ở đây ta thực hiện phép so sánh hai bit, kết quả là 1 nếu hai bit khác nhau và là 0 nếu hai bit giống nhau. Ví dụ:
    0101 (số thập phân 5)
    XOR 0011 (số thập phân 3)
    0110 (số thập phân 6)

    (cách nhớ dễ nhất là: 2 bit giống nhau trả về 0, 2 bit khác nhau trả về 1)
    Bảng chân trị cho XOR:


    ABA XOR B
    000
    011
    101
    110

    Phép toán thao tác bit XOR có thể được sử dụng để đảo ngược các bit được lựa chọn trong thanh ghi (còn được gọi là bật (set) hoặc lật (flip)). Bất kỳ bit nào được bật bằng cách thực hiện phép toán thao tác bit XOR nó với 1. Ví dụ: cho dãy bit 0010 (số 2 thập phân), bit thứ hai và thứ tư có thể được kích hoạt bằng cách sử dụng phép toán thao tác bit XOR với một dãy bit có chứa 1 ở vị trí thứ hai và thứ tư:
    0010 (số thập phân 2)
    XOR 1010 (số thập phân 10)

    = 1000 (số thập phân 8)

    Kỹ thuật này có thể được sử dụng để điều khiển dãy bit biểu hiện các bộ chứa phép toán logic đúng sai (boolean).
    Trong C, C++, Java, C#, toán tử thao tác bit XOR được biểu diễn bằng ký hiệu "^" (dấu mũ). Trong Pascal, toán tử này là "xor". Ví dụ:

    x = y ^ z; // C

    Hay:
    x:= y xor z; { Pascal }

    Câu lệnh trên sẽ gáp trình viên hợp ngữ (Assembly) thường sử dụng toán tử XOR để gán giá trị của một thanh ghi (register) về 0. Khi thực hiện phép toán XOR cho một mẫu bit với chính bản thân nó, mẫu nhị phân nhận được sẽ toàn bit 0. Trên nhiều kiến trúc máy tính, sử dụng XOR để gán 0 cho một thanh ghi sẽ được CPU xử lý nhanh hơn so với chuỗi thao tác tương ứng để nạp và lưu giá trị 0 vào thanh ghi.


    Dịch chuyển và quay bit

    Các phép dịch chuyển bit đôi khi được xem là các phép toán thao tác bit, bởi vì chúng sẽ xem một giá trị dưới dạng một dãy bit hơn là dưới dạng số lượng số (numerial quantity). Trong các phép toán này, các chữ số sẽ được di chuyển, hoặc dịch chuyển, sang trái hoặc phải. Các thanh ghi trong vi xử lý máy tính có độ dài cố định, vì vậy một vài bit sẽ bị "dịch chuyển ra ngoài" thanh ghi ở một đầu, trong khi đó thì một lượng bit tương ứng sẽ được "dịch chuyển vào" ở đầu còn lại; sự khác biệt ở các phép toán dịch chuyển bit nằm ở chỗ cách chúng xác định giá trị của các bit được dịch chuyển vào.

    Dịch chuyển số học



    Dịch chuyển số học trái

    Dịch chuyển số học phải

    Trong dịch chuyển số học, các bit được dịch chuyển ra khỏi đầu hoặc đuôi sẽ bị loại bỏ. Trong phép dịch chuyển số học về bên trái, các số 0 được dịch chuyển vào bên phải; trong phép dịch chuyển số học bên phải, bit thể hiện dấu được thêm vào bên trái, do đó dấu của số được giữ nguyên.
    Ví dụ dưới đây sử dụng thanh ghi 8-bit:
    00010111 (số thập phân +23) Dịch chuyển trái
    = 00101110 (số thập phân +46)


    10010111 (số thập phân -105) Dịch chuyển phải
    = 11001011 (số thập phân -53)

    Trường hợp đầu tiên, những số tận cùng bên trái được dịch chuyển khỏi thanh ghi, một số 0 mới được thêm vào cuối bên phải của thanh ghi. Trường hợp thứ hai, thành phần cuối bên phải đã được dịch chuyển ra khỏi, và số 1 được thêm vào bên trái, bảo toàn được dấu của số. Nhiều lần dịch chuyển có thể được rút ngắn lại còn một lần. Ví dụ:

    00010111 (số thập phân +23) Dịch sang trái 2 lần.
    = 01011100 (số thập phân +92)

    Dịch chuyển số học bên trái n lần tương đương nhân với 2n (nếu giá trị đó không gây tràn bộ nhớ), trong khi đó thì phép dịch chuyển số học sang phải n lần của một giá trị bù 2 thì tương đương với việc chia cho 2n và làm tròn về phía âm vô cùng. Nếu số nhị phân được xem là bù 1, thì phép dịch chuyển sang phải tương tự sẽ cho kết quả bằng với việc chia số đó cho 2n và làm tròn về phía 0.


    Dịch chuyển luận lý


    Left logical shift​

    Right logical shift​

    Trong dịch chuyển luận lý, các số 0 sẽ được dịch chuyển vào để thay thế các bit bị loại bỏ. Do đó dịch chuyển luận lý và dịch chuyển số học bên trái là hoàn toàn giống nhau.
    Tuy nhiên, dịch chuyển luận lý thêm giá trị 0 vào vị trí bit quan trọng nhất, thay vì sao chép bit mang dấu, điều này khá lý tưởng cho các số nhị phân không dấu, trong khi phép dịch chuyển số học sang phải thì lại lý tưởng cho các số nhị phân bù 2 có dấu.


    Quay không nhớ


    Quay trái​

    Quay phải​

    Một dạng khác của dịch chuyển được gọi là dịch chuyển vòng hay quay bit. Với phép toán này, các bit được xoay giống như là hai đầu của thanh ghi được gộp lại với nhau. Những giá trị được dịch chuyển vào ở bên phải trong một lần dịch chuyển trái chính là bất kỳ giá trị nào đã được dịch chuyển ra ở bên trái, và ngược lại. Thao tác này hữu ích nếu xảy ra yêu cầu giữ lại toàn bộ bit hiện thời, và thường được sử dụng trong mật mã học kỹ thuật số.

    Quay có nhớ

    Quay có nhớ tương tự với phép quay không nhớ, nhưng hai đầu của thanh ghi được tách ra bởi cờ nhớ (carry flag). Bit được dịch chuyển vào (ở bất kỳ đầu nào) là giá trị cũ của cờ nhớ, và bit được dịch chuyển ra (ở đầu còn lại) trở thành giá trị mới của cờ nhớ.
    Một phép quay có nhớ có thể mô phỏng một phép quay luận lý hoặc số học của một vị trí bằng cách thiết lập cờ nhớ trước tiên. Ví dụ, nếu cờ nhớ mang giá trị 0, thì x XOAY-PHẢI-CÓ-NHỚ-MỘT-LẦN là phép dịch chuyển luận lý sang phải, và nếu cờ nhớ giữ giá trị của bản sao chép của bit chứa dấu, thì x XOAY-PHẢI-CÓ-NHỚ-MỘT-LẦNlà phép dịch chuyển số học sang phải. Vì lý do này, một số vi điều khiển như các PIC tầm thấp chỉ có xoay và xoay có nhớ, mà không cần đến các cấu trúc dịch chuyển số học và luận lý.


    Dịch chuyển trong C, C++, C# và Python

    Trong các ngôn ngữ dựa trên C, các toán tử dịch chuyển trái và phải lần lượt là <<>>. Số lượng cần dịch chuyển được cung cấp ở đối số thứ hai của toán tử dịch chuyển. Ví dụ:
    x = y << 2;
    gán cho x kết quả của phép dịch chuyển y sang trái 2 bit, tương đương với phép nhân với 4.
    Trong ngôn ngữ C, kết quả của việc dịch chuyển sang phải một giá trị âm là xác định, và giá trị của phép dịch chuyển sang trái của giá trị chứa dấu là không xác định nếu kết quả không được thể hiện dưới dạng của kết quả. Trong C#, phép dịch chuyển sang phải là một phép dịch chuyển số học khi mà toán hạng là biến kiểu int hoặc long. Nếu toán hạng đầu tiên thuộc kiểu uint hoặc ulong, phép dịch chuyển sang phải là phép dịch chuyển luận lý.


    Dịch chuyển trong Java

    Trong Java, tất cả các giá trị mang kiểu số nguyên đều có dấu, và các toán tử <<>> thực hiện các phép dịch chuyển số học. Java còn thêm vào toán tử >>> để thực hiện phép dịch chuyển luận lý sang phải, nhưng bởi vì phép dịch chuyển sang trái số học và luận lý là như nhau, nên không có toán tử <<< trong Java.
    Một vài chi tiết về các toán tử dịch chuyển trong Java:

    • Thao tác << (dịch trái), >> (dịch phải có dấu), và >>> (dịch phải không dấu) được gọi là các toán tử dịch chuyển.
    • Kiểu giá trị mà phép dịch bit biểu thị là dạng cao cấp của toán hạng bên trái. Ví dụ, aByte >>> 2 thì tương đương với ((int) aByte) >>> 2.
    • Nếu như kiểu giá trị cao cấp của toán hạng bên trái là int, thì chỉ có năm bit thấp nhất theo thứ tự của toán hạng bên phải được sử dụng như là khoảng cách dịch chuyển. Điều này giống như là toán hạng bên phải được sử dụng cho một toán tử luận lý thao tác bit AND & với giá trị che đậy 0x1f (0b11111). Khoảng cách dịch chuyển thực ra luôn nằm trong khoảng từ 0 tới 31, một cách bao quát.
    • Nếu như kiểu giá trị cao cấp của toán hạng bên trái là long, thì chỉ có sáu bit thấp nhất theo tứ tự của toán hạng bên phải được sử dụng như là khoảng cách dịch chuyển. Điều đó giống như là toán hạng bên phải được sử dụng cho một toán tử luận lý thao tác bit AND & với giá trị che đậy 0x3f (0b111111). Khoảng cách dịch chuyển thực ra luôn nằm trong khoảng từ 0 tới 63, một cách bao quát.
    • Kết quả của n >>> s là n bị dịch chuyển sang phải s bit và đệm 0 vào bên trái tương ứng.
    • Trong toán tử nói chung và phép dịch bit nói riêng, kiểu dữ liệu byte được hàm ý chuyển thành int. Nếu giá trị byte đó là âm, và bit bậc cao nhất là một, thì các số một sẽ được điền vào để lấp đầy các bytes được thêm vào ở kiểu int. Do đó byte b1=-5; int i = b1 | 0x0200; sẽ cho kết quả i == -5.

    Dịch chuyển trong Pascal

    Trong Pascal, cũng như các trình biên dịch tương tự nó (như là Object Pascal và Standard Pascal), các thao tác dịch trái và dịch phải lần lượt là shlshr. Khoảng cách dịch chuyển sẽ được thêm vào trong đối số thứ hai. Ví dụ, câu lệnh sau cho x là kết quả của phép dịch y sang trái hai bit:
    x:= y shl 2;


    Ứng dụng

    Các phép toán trên thao tác bit là đặc biệt cần thiết trong các ngôn ngữ lập trình bậc thấp như các ngôn ngữ dùng để viết ra các trình cắm thiết bị (drivers), đồ họa bậc thấp, hình thành gói giao thức các truyền thông, và giải mã.
    Mặc dù các hệ thống máy thường có sẵn các cấu trúc (instructions) hiệu quả cho việc thực hiện các phép toán học và phép luận lý (logic), tuy nhiên trong thực tế, các thao thác này có thể được thực hiện bằng cách kết hợp các toán tử thao tác bit và phép thử số 0 (zero-testing) bằng nhiều cách khác nhau. Ví dụ, dưới đây là mã giải (pseudocode) của phép nhân Ai Cập cổ đại chỉ ra cách để có thể nhân hai số nguyên tùy thích ab (trong đó a lớn hơn b) mà chỉ cần sử dụng thao tác dịch chuyển bit và phép cộng:

    c = 0

    while b ≠ 0
    if (b and 1) ≠ 0
    c = c + a
    left shift a by 1
    right shift b by 1

    return c

    Một ví dụ nữa là mã giải của phép cộng, chỉ ra cách để tính tổng của hai số nguyên ab sử dụng các toán tử thao tác bit và phép thử số 0:
    while a ≠ 0
    c = b and a
    b = b xor a
    left shift c by 1
    a = c

    return b

    Lưu ý: các dấu = trong các ví dụ trên là phép gán chứ không phải là phép tương đương.




    CÁC VÍ DỤ:
    Ví dụ 1: Cho một hộp đựng có thể chứa Táo, Cam, Chanh, Bưởi, Ổi. Nếu trong hộp đựng đã chứa Táo, Cam, Chanh thì thêm Bưởi Ổi.

    Với mã dưới đây là một giải pháp chứ không phải giải thuật để giải bài toán trên:

    JavaScript:
    Sub ThemTraiCay1()
      Dim Thung, TraiCay, i, j, b As Border
      Const Tao = 1, Cam = 2, Chanh = 3, Buoi = 4, Oi = 5
      Thung = Array(Tao, Cam, Chanh)
      TraiCay = Array(Tao, Cam, Chanh, Buoi, Oi)
      For j = 0 To UBound(TraiCay)
        b = True
        For i = 0 To UBound(Thung)
          If Thung(i) = TraiCay(j) Then b = False: Exit For
        Next
        If b Then
          ReDim Preserve Thung(1 To UBound(Thung) + 1): Thung(UBound(Thung)) = TraiCay(j)
        End If
      Next
    End Sub


    Với mã dưới đây là giải thuật để giải bài toán trên nhanh và hiệu quả khi sử dụng toán tử OR:
    JavaScript:
    Sub ThemTraiCay2()
      Dim Thung, TraiCay
      Const Tao = 2 ^ 0, Cam = 2 ^ 1, Chanh = 2 ^ 2, Buoi = 2 ^ 3, Oi = 2 ^ 4
      Thung = Tao + Cam + Chanh
      TraiCay = Tao Or Cam Or Chanh Or Buoi Or Oi
      Thung= Thung Or TraiCay
    
      Debug.Print "  Tao: "; Tao And Thung
      Debug.Print "  Cam: "; Cam And Thung
      Debug.Print "Chanh: "; Chanh And Thung
      Debug.Print " Buoi: "; Buoi And Thung
      Debug.Print "   Oi: "; Oi And Thung
    End Sub


    Ví dụ 2: Loại bỏ Cam và Chanh trong thùng trái cây, ta dùng toán tử AND và NOT.
    JavaScript:
    Sub LoaiBoTraiCay1()
      Dim Thung, TraiCay
      Const Tao = 2 ^ 0, Cam = 2 ^ 1, Chanh = 2 ^ 2, Buoi = 2 ^ 3, Oi = 2 ^ 4
      Thung = Tao + Cam + Chanh + Buoi + Oi
      Thung = Thung And Not Chanh And Not Cam
      Debug.Print "  Tao: "; Tao And Thung
      Debug.Print "  Cam: "; Cam And Thung
      Debug.Print "Chanh: "; Chanh And Thung
      Debug.Print " Buoi: "; Buoi And Thung
      Debug.Print "   Oi: "; Oi And Thung
    End Sub


    Ví dụ 3: Tìm trái cây giống nhau trong hai thùng trái cây ta dùng toán tử AND:
    JavaScript:
    Sub TrungTraiCay1()
      Dim Thung1, Thung2, TraiCay
      Const Tao = 2 ^ 0, Cam = 2 ^ 1, Chanh = 2 ^ 2, Buoi = 2 ^ 3, Oi = 2 ^ 4
      Thung1 = Tao + Cam + Chanh + Buoi
      Thung2 = Chanh + Buoi + Oi
      TraiCay = Thung1 And Thung2
      Debug.Print "  Tao: "; Tao And TraiCay
      Debug.Print "  Cam: "; Cam And TraiCay
      Debug.Print "Chanh: "; Chanh And TraiCay
      Debug.Print " Buoi: "; Buoi And TraiCay
      Debug.Print "   Oi: "; Oi And TraiCay
    End Sub


    Các bạn sẽ thấy tôi đã khai báo các mục Trái Cây là lũy thừa cơ số 2, tức là đơn vị bit 0 và 1
    JavaScript:
    Const Tao = 2 ^ 0, Cam = 2 ^ 1, Chanh = 2 ^ 2, Buoi = 2 ^ 3, Oi = 2 ^ 4

    Hoặc dạng HEXADECIMAL:
    JavaScript:
    Const Tao =&H1, Cam = &H2, Chanh = &H4, Buoi = &H8, Oi = &H10

    Đặt chi trái cây, ví dụ Cam, Chanh và Bưởi cùng một chi:
    JavaScript:
    Const Citrus = Cam + Chanh + Buoi

    Thay vì sử dụng dấu +, hãy sử dụng OR:
    JavaScript:
    Const Citrus = Cam OR Chanh OR Buoi

    Tại sao OR mà không phải +, chỉ cần bạn thêm Chanh vào là sẽ rõ, toán tử OR sẽ gộp chứ không cộng dồn:
    JavaScript:
    Const Citrus = Cam + Chanh + Chanh + Buoi
    Là một giá trị chỉ chứa Cam, nhưng không thuộc các mục khai báo khác.

    JavaScript:
    Const Citrus = Cam OR Chanh OR Chanh OR Buoi
    Thì giá trị vẫn là Cam + Chanh + Buoi

    Các bạn cũng có thể sử dụng số âm để đặt các mục, phủ định của một số không ÂM là số ÂM, nên ta sử dụng toán tử NOT:
    JavaScript:
    Const Tao = NOT 2 ^ 0, Cam =NOT 2 ^ 1, Chanh =NOT 2 ^ 2, Buoi =NOT 2 ^ 3, Oi =NOT 2 ^ 4






    Vì chưa có thời gian để viết bài thêm: nên tạm thời tôi dừng ở đây.
    ****Bài viết sẽ được tiếp tục cập nhật thêm****
     
    Lần chỉnh sửa cuối:
    Giải pháp
    CẬP NHẬT BÀI VIẾT

    Các phép toán cộng, trừ, nhân, chia, chia dư và lũy thừa chỉ sử dụng phép toán bitwise, các toán tử dịch chuyển bit bên dưới mã chỉ hoạt động trong TwinBasic.
    Để thay thế và sử dụng trong VBA hãy thay thế các vị trí mã chuyển bit << và >>, thành dịch chuyển trái LShift và dịch chuyển phải RShift.


    Các đoạn mã bên dưới chỉ là ví dụ để các bạn hiểu cách các phép toán hoạt động như thế nào.

    1. Cộng hai số bằng bitwise:

    JavaScript:
    Function BitwiseAdd(a As Long, b As Long) As Long
        Dim carry As Long
        Do While b <> 0
            carry = a And b
            a = a Xor b
            b = carry << 1
        Loop
        BitwiseAdd = a
    End Function
    
    
    Function BitwiseAddDecimal(a As Currency, b As Currency, Optional precision...
    Hôm nay tôi chia sẻ đến các bạn một kiến thức, mà hầu như các Lập trình viên sơ cấp, không được giảng rỏ về 4 toán tử NOT, OR, AND và XOR căn bản trong mọi ngôn ngữ lập trình.

    NOT, OR, AND và XOR không phải trả về giá trị đúng sai như các bạn thường vận dụng. Vì chúng là toán tử, chính xác thì chúng là toán tử tính toán chỉ khác cộng, trừ, nhân, chia, ..., chúng tính toán dựa vào phép toán bitwise.
    Nhưng ít sách vở căn bản nào giải thích rõ cho các bạn biết bản chất của chúng.

    Chính vì vậy mà hôm nay tôi chia sẻ thêm kiến thức tuy căn bản nhưng lại là tiền đề cho những cấu trúc mã Nâng cao sau này. Vì chúng là cơ sở tạo các cấu trúc mã với giải thuật, thuật toán sau rộng hơn trong Lập trình. Vì chúng không chỉ có trong một ngôn ngữ là VBA.

    Để bắt đầu chủ đề các bạn cần xem kiến thức cơ bản về các toán tử logic NOT, OR, XOR và AND tại Wikipedia:
    (Tiếng Anh) https://en.wikipedia.org/wiki/Bitwise_operation
    (Tiếng Việt) https://vi.wikipedia.org/wiki/Phép_toán_thao_tác_bit


    Ví dụ:
    Ví dụ 1: Cho một hộp đựng có thể chứa Táo, Cam, Chanh, Bưởi, Ổi. Nếu trong hộp đựng đã chứa Táo, Cam, Chanh thì thêm Bưởi và Ổi.

    Với mã dưới đây là một giải pháp chứ không phải giải thuật để giải bài toán trên:

    JavaScript:
    Sub ThemTraiCay1()
      Dim Thung, TraiCay, i, j, b As Border
      Const Tao = 1, Cam = 2, Chanh = 3, Buoi = 4, Oi = 5
      Thung = Array(Tao, Cam, Chanh)
      TraiCay = Array(Tao, Cam, Chanh, Buoi, Oi)
      For j = 0 To UBound(TraiCay)
        b = True
        For i = 0 To UBound(Thung)
          If Thung(i) = TraiCay(j) Then b = False: Exit For
        Next
        If b Then
          ReDim Preserve Thung(1 To UBound(Thung) + 1): Thung(UBound(Thung)) = TraiCay(j)
        End If
      Next
    End Sub

    Với mã dưới đây là giải thuật để giải bài toán trên nhanh và hiệu quả:
    JavaScript:
    Sub ThemTraiCay2()
      Dim Thung, TraiCay
      Const Tao = 2 ^ 0, Cam = 2 ^ 1, Chanh = 2 ^ 2, Buoi = 2 ^ 3, Oi = 2 ^ 4
      Thung = Tao + Cam + Chanh
      TraiCay = Tao Or Cam Or Chanh Or Buoi Or Oi
      Thung= Thung Or TraiCay
    
      Debug.Print "  Tao: "; Tao And Thung
      Debug.Print "  Cam: "; Cam And Thung
      Debug.Print "Chanh: "; Chanh And Thung
      Debug.Print " Buoi: "; Buoi And Thung
      Debug.Print "   Oi: "; Oi And Thung
    End Sub


    Vì chưa có thời gian để viết bài thêm: nên tạm thời tôi dừng ở đây.
    ****Bài viết sẽ được tiếp tục cập nhật thêm****
    Đang không hiểu thấy chạy thử thì Táo, Cam, Chạy không có cộng thêm bưởi vào! thấy vẫn 1, 2,4 đúng ra phải 25, 26, 28 không biết phải không?
    1678096581161.png
     
    Upvote 0
    Đang không hiểu thấy chạy thử thì Táo, Cam, Chạy không có cộng thêm bưởi vào! thấy vẫn 1, 2,4 đúng ra phải 25, 26, 28 không biết phải không?
    Bạn chưa đọc qua Wikipedia về phép toán Bitwise, để hiểu được giải thuật trên. Bitwise không phải toán cộng hay trừ mà là dịch chuyển bit, quay bit.
     
    Lần chỉnh sửa cuối:
    Upvote 0
    Bạn chưa đọc qua Wikipedia về phép toán Bitwise, để hiểu được giải thuật trên. Bitwise không phải toán cộng hay trừ mà là dịch chuyển bit, quay bit.
    bác có update mới phiên bản này chưa bác. Thật sự em ko nghĩ là Excel có thế toàn năng như thế. Tks bác đã nâng tầm excel
     
    Upvote 0
    Đang không hiểu thấy chạy thử thì Táo, Cam, Chạy không có cộng thêm bưởi vào! thấy vẫn 1, 2,4 đúng ra phải 25, 26, 28 không biết phải không?
    View attachment 287305
    Bạn chuyển hết những số này sang dạng nhị phân, sau đó dùng toán tử OR, AND và NOT và đọc bài viết trên Wikipedia là sẽ hiểu.
    AND dùng để kiểm tra một bit cờ nào đó được thiết đặt (1) hay chưa (0), OR dùng để bật/tắt bit cờ, còn OR để đảo ngược bit.
    Một ví dụ thường thấy là dùng để lưu trữ giá trị kiểu boolean, thay vì khai báo kiểu boolean tốn mấy byte thì dùng toán tử thao tác bit OR để bật tắt cờ bit (1 - TRUE, 0 - FALSE) tốn có vài bit, giúp tiết kiệm bộ nhớ.
    Ngoài ra còn dịch chuyển bit (bit shift), dịch chuyển trái hoặc dịch chuyển phải, rất tiếc là VB/VBA không hỗ trợ toán tử này.
     
    Upvote 0
    ...
    Một ví dụ thường thấy là dùng để lưu trữ giá trị kiểu boolean, thay vì khai báo kiểu boolean tốn mấy byte thì dùng toán tử thao tác bit OR để bật tắt cờ bit (1 - TRUE, 0 - FALSE) tốn có vài bit, giúp tiết kiệm bộ nhớ.
    ...
    Thời buổi bi giờ tiết kiệm bộ nhớ không còn là vấn đề quan trọng nữa.
    Vấn đề là truy cập có nhanh hay không. Hiện nay, Long là loại truy cập hiệu quả nhất.
    Người ta sử dụng phép toán bit với lý do chính là bọn này gần với phép tính căn bản của CPU, rất rất nhanh.
    Ví dụ ăn bản nhất là con toán xét chẵn/lẻ. Rất nhiều bạn có thói quen dùng hàm hoặc toán tử Mod để tính. Đó là thói quen ấu trĩ lập trình.
    Ta biết số lẻ có bit 1 là 1 và số chẵn có bit này là 0
    Phép toán AND 1 hiệu quả nhất để làm việc này.
    If (a AND 1) a là số lẻ.
    If (a AND 1) = 0 a là số chẵn
    Function IsOdd(a as long) as boolean
    If (a AND 1) IsOdd = True
    End Function
    Function IsEven(a as long) as boolean
    If (a AND 1)=0 IsEven = True
    End Function
    Hai hàm này vốn có sẵn trong Excel. Rất tiếc là còn rất nhiều bạn dùng hàm Mod(a, 2) để tính.

    Bài toán dựng cờ bit là một ứng dụng khác hoàn toàn. Nó dùng trong một số trường hợp có thể dùng trị nguyên để phân biệt các loại trong một tổng loại. Ví dụ cam táo trên là một trường hợp, tổng loại là trái cây, phân loại là cam táo...

    ' xét xem có những loại trái cây nào trong thùng
    For Each quả trong thùng
    Select Case quả
    Case cam: qtt = qtt OR cam
    Case táo: qtt = qtt OR táo
    ...
    End Select
    Next quả
    ' Code xét xem có bao nhiêu loại quả trong thùng
    Cộng số bit của qtt, bao nhiêu bit là bây nhiêu loại quả
    ' Code thêm vào cho đủ loại quả
    For Each loại quả in tổng loại quả
    qtt = qtt OR loại quả
    Next loại quả

    Lưu ý: khi tính các loại quả thì nên cẩn thận với phép cộng. Phép cộng chỉ cộng được một lần, qua lầ thứ hai sẽ ra kết quả sai. Phép OR thì bao nhiêu lần cũng được.
     
    Lần chỉnh sửa cuối:
    Upvote 0
    Thời buổi bi giờ tiết kiệm bộ nhớ không còn là vấn đề quan trọng nữa.
    Vấn đề là truy cập có nhanh hay không. Hiện nay, Long là loại truy cập hiệu quả nhất.
    Người ta sử dụng phép toán bit với lý do chính là bọn này gần với phép tính căn bản của CPU, rất rất nhanh.
    Ví dụ ăn bản nhất là con toán xét chẵn/lẻ. Rất nhiều bạn có thói quen dùng hàm hoặc toán tử Mod để tính. Đó là thói quen ấu trĩ lập trình.
    Ta biết số lẻ có bit 1 là 1 và số chẵn có bit này là 0
    Phép toán AND 1 hiệu quả nhất để làm việc này.
    If (a AND 1) a là số lẻ.
    If (a AND 1) = 0 a là số chẵn

    Bài toán dựng cờ bit là một ứng dụng khác hoàn toàn. Nó dùng trong một số trường hợp có thể dùng trị nguyên để phân biệt các loại trong một tổng loại. Ví dụ cam táo trên là một trường hợp, tổng loại là trái cây, phân loại là cam táo...

    ' xét xem có những loại trái cây nào trong thùng
    For Each quả trong thùng
    Select Case quả
    Case cam: qtt = qtt OR cam
    Case táo: qtt = qtt OR táo
    ...
    End Select
    Next quả
    ' Code xét xem có bao nhiêu loại quả trong thùng
    Cộng số bit của qtt, bao nhiêu bit là bây nhiêu loại quả
    ' Code thêm vào cho đủ loại quả
    For Each loại quả in tổng loại quả
    qtt = qtt OR loại quả
    Next loại quả

    Lưu ý: khi tính các loại quả thì nên cẩn thận với phép cộng. Phép cộng chỉ cộng được một lần, qua lầ thứ hai sẽ ra kết quả sai. Phép OR thì bao nhiêu lần cũng được.
    Tiết kiệm bộ nhớ vẫn quan trọng nhé, nhất là khi lập trình nhúng bằng C++ (bộ nhớ RAM hạn chế), viết driver (gpu, thiết bị ngoại vi, v.v,...), v.v., nên cách thức trên vẫn được sử dụng nhiều, các struct của Win32 API vẫn sử dụng cách này để tối ưu sử dụng bộ nhớ nhất có thể, nhiều tham số các hàm cung cấp được đóng gói (pack) vào kiểu DWORD dưới dạng low order 16bit WORD và high order 16bit WORD (những ai lập trình Window desktop chắc không lạ gì tham số wParam, lParam và sử dụng các macro như LOWORD và HIWORD, LOBYTE, HIBYTE thực chất sử dụng những phép dịch chuyển bit trái phải).
    Còn vấn đề dùng toán tử thao tác bit thay cho một số phép toán cơ bản thì đôi khi vẫn sử dụng do tốc độ của nó mang lại tốt hơn, vd: Tính căn bậc hai hoặc lũy thừa của một số thì dùng phép dịch chuyển bit trái hoặc phải cho hiệu năng tốt hơn.
     
    Upvote 0
    CẬP NHẬT BÀI VIẾT

    Các phép toán cộng, trừ, nhân, chia, chia dư và lũy thừa chỉ sử dụng phép toán bitwise, các toán tử dịch chuyển bit bên dưới mã chỉ hoạt động trong TwinBasic.
    Để thay thế và sử dụng trong VBA hãy thay thế các vị trí mã chuyển bit << và >>, thành dịch chuyển trái LShift và dịch chuyển phải RShift.


    Các đoạn mã bên dưới chỉ là ví dụ để các bạn hiểu cách các phép toán hoạt động như thế nào.

    1. Cộng hai số bằng bitwise:

    JavaScript:
    Function BitwiseAdd(a As Long, b As Long) As Long
        Dim carry As Long
        Do While b <> 0
            carry = a And b
            a = a Xor b
            b = carry << 1
        Loop
        BitwiseAdd = a
    End Function
    
    
    Function BitwiseAddDecimal(a As Currency, b As Currency, Optional precision As Long = 5)) As Currency
        Dim scale As Currency
        Dim intA As Currency, intB As Currency, intResult As Currency
        Dim divisor As Currency, quotient As Currency
    
        ' Chuyển đổi các tham số thành kiểu nguyên với độ chính xác (precision)
        scale = CCur(BitwisePower(CLng(10), precision)) ' Lũy thừa với bitwise, chuyển về Currency
        intA = CCur(BitwiseMultiply(CLng(a), CLng(scale))) ' Nhân sử dụng bitwise, chuyển về Currency
        intB = CCur(BitwiseMultiply(CLng(b), CLng(scale))) ' Nhân sử dụng bitwise, chuyển về Currency
    
        ' Cộng hai giá trị sử dụng phép cộng bitwise
        intResult = CCur(BitwiseAdd(CLng(intA), CLng(intB)))
    
        ' Thay thế phép chia bằng các phép trừ và dịch chuyển bitwise
        divisor = scale
        quotient = 0
        Do While intResult >= divisor
            divisor = divisor * 2 ' Tương đương với dịch trái (<< 1)
        Loop
        Do
            divisor = divisor / 2 ' Tương đương với dịch phải (>> 1)
            If intResult >= divisor Then
                intResult = CCur(BitwiseSubtract(CLng(intResult), CLng(divisor))) ' Trừ bằng bitwise
                quotient = quotient + 1 ' Thay thế cộng bằng kiểu Currency
            End If
        Loop While divisor >= scale
    
        ' Kết quả cuối cùng
        BitwiseAddDecimal = quotient
    End Function

    2. Trừ hai số bằng cách đảo bít của số thứ hai và sử dụng phép cộng:


    JavaScript:
    Function BitwiseSubtract(a As Long, b As Long) As Long
        BitwiseSubtract = BitwiseAdd(a, BitwiseAdd(Not b, 1))
    End Function

    3. Nhân hai số bằng cách dịch chuyển và cộng:

    JavaScript:
    Function BitwiseMultiply(a As Long, b As Long) As Long
        Dim result As Long
        result = 0
        Do While b <> 0
            If (b And 1) <> 0 Then
                result = BitwiseAdd(result, a)
            End If
            a = a << 1
            b = b >> 1
        Loop
        BitwiseMultiply = result
    End Function

    4. Chia hai số sử dụng phép trừ và dịch chuyển:


    JavaScript:
    Function BitwiseDivide(a As Long, b As Long) As Long
        Dim result As Long, temp As Long
        result = 0
        temp = b
        Do While a >= temp
            temp = temp << 1
        Loop
        Do
            temp = temp >> 1
            If a >= temp Then
                a = BitwiseSubtract(a, temp)
                result = BitwiseAdd(result, 1)
            End If
        Loop While temp >= b
        BitwiseDivide = result
    End Function

    5. Phép chia lấy dư (modulo)

    JavaScript:
    Function BitwiseModulo(a As Long, b As Long) As Long
        Dim temp As Long
        temp = b
        Do While a >= temp
            temp = temp << 1
        Loop
        Do
            temp = temp >> 1
            If a >= temp Then
                a = BitwiseSubtract(a, temp)
            End If
        Loop While temp >= b
        BitwiseModulo = a
    End Function

    5. Phép lũy thừa

    JavaScript:
    Function BitwisePower(base As Long, exponent As Long) As Long
        Dim result As Long
        result = 1
        Do While exponent > 0
            If (exponent And 1) <> 0 Then
                result = BitwiseMultiply(result, base)
            End If
            base = BitwiseMultiply(base, base)
            exponent = exponent >> 1
        Loop
        BitwisePower = result
    End Function

    6. Phép dịch chuyển bit trái và phải

    JavaScript:
    Function RShift(ByVal lNum As Long, ByVal lBits As Long) As Long
        If lBits <= 0 Then RShift = lNum
        If (lBits <= 0) Or (lBits > 31) Then Exit Function
     
        RShift = (lNum And (2 ^ (31 - lBits) - 1)) * _
            IIf(lBits = 31, &H80000000, 2 ^ lBits) Or _
            IIf((lNum And 2 ^ (31 - lBits)) = 2 ^ (31 - lBits), _
            &H80000000, 0)
    End Function
    
    Function LShift(ByVal lNum As Long, ByVal lBits As Long) As Long
        If lBits <= 0 Then LShift = lNum
        If (lBits <= 0) Or (lBits > 31) Then Exit Function
     
        If lNum < 0 Then
            LShift = (lNum And &H7FFFFFFF) \ (2 ^ lBits) Or 2 ^ (31 - lBits)
        Else
            LShift = lNum \ (2 ^ lBits)
        End If
    End Function
    
    Property Get LoWord(dwNum As Long) As Integer
        LoWord = dwNum And &HFFFF
    End Property
    
    Property Let LoWord(dwNum As Long, ByVal wNewWord As Integer)
        dwNum = dwNum And &HFFFF0000 Or wNewWord
    End Property
    
    Property Get HiWord(dwNum As Long) As Integer
        HiWord = ((dwNum And IIf(dwNum < 0, &H7FFF0000, &HFFFF0000)) \ &H10000) Or (-(dwNum < 0) * &H8000)
    End Property
    
    Property Let HiWord(dwNum As Long, ByVal wNewWord As Integer)
        dwNum = dwNum And &HFFFF& Or IIf(wNewWord < 0, ((wNewWord And &H7FFF)  * &H10000) Or &H80000000, wNewWord * &H10000)
    End Property
    
    Property Get LoByte(wNum As Integer) As Byte
        LoByte = wNum And &HFF
    End Property
    
    Property Let LoByte(wNum As Integer, ByVal btNewByte As Byte)
        wNum = wNum And &HFF00 Or btNewByte
    End Property
    
    Property Get HiByte(wNum As Integer) As Byte
        HiByte = (wNum And &HFF00&) \ &H100
    End Property
    
    Property Let HiByte(wNum As Integer, ByVal btNewByte As Byte)
        wNum = wNum And &HFF Or (btNewByte * &H100&)
    End Property


    Người lập trình mục tiêu hướng đến là Độ phức tạp thời gian, Mã ngắn, Tối ưu hóa, Tốc độ, Đồng bộ, Đa luồng, Xử lý memory, Mã máy, Xử lý lỗi, tiết kiệm chi phí, tiện dụng.

    Bất kì khả năng nào trong lập trình, đều cần thiết cho một giải thuật, giải pháp, thuật toán.
     
    Lần chỉnh sửa cuối:
    Upvote 0
    Giải pháp
    Tiết kiệm bộ nhớ vẫn quan trọng nhé, nhất là khi lập trình nhúng bằng C++ (bộ nhớ RAM hạn chế), viết driver (gpu, thiết bị ngoại vi, v.v,...), v.v., nên cách thức trên vẫn được sử dụng nhiều, các struct của Win32 API vẫn sử dụng cách này để tối ưu sử dụng bộ nhớ nhất có thể, nhiều tham số các hàm cung cấp được đóng gói (pack) vào kiểu DWORD dưới dạng low order 16bit WORD và high order 16bit WORD (những ai lập trình Window desktop chắc không lạ gì tham số wParam, lParam và sử dụng các macro như LOWORD và HIWORD, LOBYTE, HIBYTE thực chất sử dụng những phép dịch chuyển bit trái phải).

    Hình như có sự xáo trộn giữa C và C++ đâu đó.
    Nhưng mà thôi, chúng ta đi xa đề quá rồi. Bạn nói về Xi đậm đậm Xi lợt lợt trong Windows, tôi thì quen với tụi này trong platform khác. Khi học Xi lợt lợt thì tôi học STL (tiêu chuẩn) chứ không đá động gì đến Win API.

    Vả lại bài này bắt đầu từ VBA, và thắc mắc ở bài #2 liên quan đến kiến thức về áp dụng enum và bit values. Bài #1 muốn giản dị ví dụ cho nên không theo sát với enum, khiến code khó hiểu hơn. (Tôi không nói chuyện sai đúng. Tôi chỉ nói về độ khó hiểu.)

    Còn vấn đề dùng toán tử thao tác bit thay cho một số phép toán cơ bản thì đôi khi vẫn sử dụng do tốc độ của nó mang lại tốt hơn, vd: Tính căn bậc hai hoặc lũy thừa của một số thì dùng phép dịch chuyển bit trái hoặc phải cho hiệu năng tốt hơn.
    Thời buổi bi giờ, các trình dịch thông minh đủ sức để cải tiến lúc dịch sang mã máy. Người viết code C++ không cần phải lo.
    Chỉ cần lúc dịch thì chọn "code optimization".
     
    Upvote 0
    Thời buổi bi giờ, các trình dịch thông minh đủ sức để cải tiến lúc dịch sang mã máy. Người viết code C++ không cần phải lo.
    Chỉ cần lúc dịch thì chọn "code optimization".
    Nói chung là nhiều người hơi nhạy cảm vấn đề dùng toán tử thao tác bit thay cho phép toán số học vì cho rằng trình biên dịch sẽ không tự tối ưu code, tuy nhiên trình biên dịch hiện nay đã đủ thông minh để nó tự biết nên tối ưu ở chỗ nào và khi nào thì cần can thiệp. Nhiều người đồng tình rằng nên dùng phép toán số học để dễ đọc hơn, tiện bảo trì code, còn việc tối ưu code cứ để cho trình biên dịch làm việc của nó.

    1742811642683.png
    Xem thêm: What is the fastest way to find if a number is even or odd?
     
    Upvote 0
    Bài này nếu có giải thích chi tiết từng bước thì sẽ dễ hiểu hơn, kiểu như tại sao dùng toán tử bit AND, OR, NOT, XOR, dịch chuyển bit ở đây chẳng hạn.
    Ví dụ: Đóng gói hai giá trị giá trị uint16 vào một giá trị uint32.
    Cho hai giá trị uint16 lần lượt là 0xABCD (43891) và 0xBCDE (48350), đầu tiên chuyển 0xABCD sang kiểu uint32, sau đó dịch chuyển trái 16bit, cuối cùng chuyển 0xBCDE sang kiểu uint32 và dùng toán tử bit OR để ghi vào vị trí mà 0xABCD đã chuyển đi bằng phép dịch chuyển bit trên.
    Kết quả cuối cùng là 0xABCDBCDE (2882387166), trong đó 0xABCD (43891) là high order bit, còn 0xBCDE (48350) là low order bit.
    Từ đây có thể viết thêm hàm HIWORD và LOWORD để trích xuất lần lượt high order bit và low order bit của giá trị.
     

    File đính kèm

    • IMG_20250325_094218.jpg
      IMG_20250325_094218.jpg
      117.4 KB · Đọc: 9
    Upvote 0
    Web KT

    Bài viết mới nhất

    Back
    Top Bottom