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...


Đ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?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****

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.Đ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.
www.giaiphapexcel.com




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.Đ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
Thời buổi bi giờ tiết kiệm bộ nhớ không còn là vấn đề quan trọng nữa....
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ớ.
...




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).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.
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
Function BitwiseSubtract(a As Long, b As Long) As Long
BitwiseSubtract = BitwiseAdd(a, BitwiseAdd(Not b, 1))
End Function
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
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
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
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
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
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).
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.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.




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ó.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".







Để ý rằng tiêu đề bài này bắt đầu bằng "VBA nâng cao". Nếu bạn có kinh nghiệm đọc bài của chủ thớt thì cũng biết từ "cao" của thớt có nghĩa là rất cao.Bài viết hay nhưng mong mọi người thêm các ví dụ nhiều hơn. cách đặt các hằng số bit để dùng toán tử OR và AND như thế nào?


Để ý rằng tiêu đề bài này bắt đầu bằng "VBA nâng cao". Nếu bạn có kinh nghiệm đọc bài của chủ thớt thì cũng biết từ "cao" của thớt có nghĩa là rất cao.
Bạn hiểu được bài #1, #5, #7, #8 chúng nói gì thì trình độ lập trình đã cao lắm rồi.
Ứng dụng vào VBA, các "hằng số bit" mà bạn muốn hỏi chúng thuộc về lý thuyết enum nhiều hơn là đại số nhị phân (binary algebra). Bạn muốn ví dụ về lý thuyết hay thực hành?
Tìm một ví dụ thật đơn giản thì diễn tả không hết, quý vị sẽ chẳng thấy kỹ thuật bit flagging có lợi gì cả. Tôi cố gắng lắm nhưng chỉ thấy ví dụ trung bình là đáp ứng được điều kiệ không quá phức tạp thôi.Vì nội dung này em thấy khó nên tuỳ kinh nghiệm các anh làm sao để em và mọi người hiểu để ứng dụng. có thể cho các ví dụ liên quan đến trình bày trên userform hoặc dữ liệu trên sheet? Bước đầu cứ cơ bản thôi ạ.
Hình như có liên quan đến kiến thức nhị phân 0101 gì đó nếu được anh trình bày chi tiết phần này thì tốt quá ạ.



Tìm một ví dụ thật đơn giản thì diễn tả không hết, quý vị sẽ chẳng thấy kỹ thuật bit flagging có lợi gì cả. Tôi cố gắng lắm nhưng chỉ thấy ví dụ trung bình là đáp ứng được điều kiệ không quá phức tạp thôi.
Bit flag có nghĩa là coi mỗi bít như có lá cờ cắm ở vị trí ấy. Ta có thể tưởng tượng flag (nghiêng) về bên trái thì là OFF, giá trị 0 ở vị trí ấy; flag về bên phải thì là ON, giá trị 1 ở vị trí ấy.
Điển hình loại biến integer 16 bits:
View attachment 307694
Hy vọng ví dụ trên giúp quý vị hiểu rõ thế nào là bit set/flag On/Off
Nếu trên Form thì ta có thể tưởng tượng On/Off theo kiểu của check box.
Một cách áp dụng điển hình của set bit là để diễn tả một array giá trị True/False. Một biến Long có thể diễn tả được một array 31 phần tử. Bit thứ 32 của Long dùng để đặt dấu (+-) cho nên cần tránh.
Đến đây, nếu quý vị nghĩ rằng chúng có thể dùng để "tiết kiệm bộ nhớ" (thay vì 31 bytes thì chỉ cần 4 thôi) thì là sai tét bét. Chủ đích của ví dụ này là để truyền tham số.
Hàm ABC gọi hàm DEF, ngoài các tham số cần thiết, có thể đặt thêm một số OPTIONS. Cách thông thường mà quý vị dùng là Optional Paramarray với một đống Yes/No (True/False). Dùng phương pháp set và xét bits sẽ giản dị hơn nhiều:
Enum VIDU
Option1 = 1
Option2 = 2
Option3 = 4
Option5 = 8
...
End Enum
Sub DEF(t1 As Long, t2 As String, Ops As Long)
If Ops AND VIDU.Option1 Then ' option 1 được chọn
If Ops AND VIDU.Option5 Then ' option 5 được chọn
...
End Sub
Sub ABC(....)
...
' gọi DEF và chọn options 2, 3
DEF 0, "", 2+4
' hoặc
DEF 0, "", VIDU.Option2 + VIDU.Option3
...
End Sub


Tìm một ví dụ thật đơn giản thì diễn tả không hết, quý vị sẽ chẳng thấy kỹ thuật bit flagging có lợi gì cả. Tôi cố gắng lắm nhưng chỉ thấy ví dụ trung bình là đáp ứng được điều kiệ không quá phức tạp thôi.
Bit flag có nghĩa là coi mỗi bít như có lá cờ cắm ở vị trí ấy. Ta có thể tưởng tượng flag (nghiêng) về bên trái thì là OFF, giá trị 0 ở vị trí ấy; flag về bên phải thì là ON, giá trị 1 ở vị trí ấy.
Điển hình loại biến integer 16 bits:
View attachment 307694
Hy vọng ví dụ trên giúp quý vị hiểu rõ thế nào là bit set/flag On/Off
Nếu trên Form thì ta có thể tưởng tượng On/Off theo kiểu của check box.
Một cách áp dụng điển hình của set bit là để diễn tả một array giá trị True/False. Một biến Long có thể diễn tả được một array 31 phần tử. Bit thứ 32 của Long dùng để đặt dấu (+-) cho nên cần tránh.
Đến đây, nếu quý vị nghĩ rằng chúng có thể dùng để "tiết kiệm bộ nhớ" (thay vì 31 bytes thì chỉ cần 4 thôi) thì là sai tét bét. Chủ đích của ví dụ này là để truyền tham số.
Hàm ABC gọi hàm DEF, ngoài các tham số cần thiết, có thể đặt thêm một số OPTIONS. Cách thông thường mà quý vị dùng là Optional Paramarray với một đống Yes/No (True/False). Dùng phương pháp set và xét bits sẽ giản dị hơn nhiều:
Enum VIDU
Option1 = 1
Option2 = 2
Option3 = 4
Option5 = 8
...
End Enum
Sub DEF(t1 As Long, t2 As String, Ops As Long)
If Ops AND VIDU.Option1 Then ' option 1 được chọn
If Ops AND VIDU.Option5 Then ' option 5 được chọn
...
End Sub
Sub ABC(....)
...
' gọi DEF và chọn options 2, 3
DEF 0, "", 2+4
' hoặc
DEF 0, "", VIDU.Option2 + VIDU.Option3
...
End Sub
Enum VIDU
Option0 = &H0
Option1 = &H1
Option2 = &H2
Option3 = &H4
Option4 = &H8
Option5 = &H10
Option6 = &H20
...
OptionN1 = &H20 OR Option3
OptionN2 = &H20 OR Option3 OR Option4
End Enum


Giá trị đặt làm hằng số Enum bạn có thể dựa vào giá trị Hexadecimal
JavaScript:Enum VIDU Option0 = &H0 Option1 = &H1 Option2 = &H2 Option3 = &H4 Option4 = &H8 Option5 = &H10 Option6 = &H20 ... OptionN1 = &H20 OR Option3 OptionN2 = &H20 OR Option3 OR Option4 End Enum
Quy tắc ghi nhớ rất dễ nhớ, với bốn số 1, 2, 4, 8 khi tăng dần lên cứ thêm số 0 vào đằng sau
Tuy nhiên kiểu giá trị Enum chỉ có giới hạn giá trị là Long. Tức là &H7FFFFFFF.
Để thiết lập số nguyên lớn nhất trong win64 thì phải sử dụng khai báo const với số lớn nhất là &H7FFFFFFFFFFFFFFF^
Hệ Hexadecimal lại có định nghĩa bổ sung là một cách biểu diễn số nguyên có dấu trong máy tính, giúp xử lý phép toán cộng/trừ dễ dàng và tránh sự mơ hồ khi làm việc với số âm. Gồm One's Complement (bổ sung 1) và Two's Complement (bổ sung 2).
Tùy theo cách bạn ép kiểu như thế nào thì giá trị Hex sẽ trả về theo kiểu đó
Nếu bạn nhập Hex là &HFFFF thì giá trị thực tế là -1 nếu bạn ép kiểu Integer, là 65535 nếu bạn ép kiểu Long
Nếu bạn nhập &HFFFF& thì giá trị của chúng bắt buộc là 65535
Trong phép toán Bitwise khi tính thì &HFFFF = -1 và &HFFFF& = 65535 là do phép bổ sung 2, nên chúng là như nhau
Nếu bạn dùng toán tử như sau, thì chúng sẽ trả về chỉ một giá trị:
-1 AND 65535 sẽ là 65535
Không ép kiểu (&HFFFF OR 65535) sẽ là -1, Vì lúc này VBA đã ép kiểu của giá trị 65535 thành -1, nên giá trị trả về là -1
Và ép kiểu (&HFFFF& OR 65535) sẽ là 65535, vì VBA không ép kiểu của giá trị 65535.
Công thức thì nó vậy. Nhưng trên thực tế, tôi cứ việc nhân 2 trị trước cho nó gọn và dễ hiểu.Cách để tạo hằng số bit như cấu trúc Enum ví dụ trên là
Enum VIDU
Option1 = 1
Option2 = 2
Option3 = 4
Option5 = 8
...
End Enum
Như vậy các giá trị 1, 2, 4, 8,.. bắt buộc dùng công thức 2^(vị trí bit-1) phải không ạ?


Công thức thì nó vậy. Nhưng trên thực tế, tôi cứ việc nhân 2 trị trước cho nó gọn và dễ hiểu.