Hey Developers!
Have you ever stumbled by the Bit32 Library and wondered what it is for? Well this tutorial will help you understand the Binary System, Bit32 functions and how & where to use them.
1 - The Binary System
How to read Binary
In Binary, numbers are represented in bits
, that can either be on or off (also known as 0 and 1).
Bits by themselves aren’t very useful and so they are often grouped into bytes
, which is made up of 8 bits and can represent 256 different values.
This system of counting that bits work on is the base-2 method, where each bit is the value of all the bits before it + 1.
An example of this, lets say you got a byte of data. You want to find the Value of it, but how do you do it?
Here is a simple trick: The Value of the Bit is exactly 2x the value of the bit before.
So the 1st bit (bit 0 as it is called) would be worth 1, so work out what this value would be:
0000 0001
of course, it is 1. Now here is how we would represent 42
0010 1010
Remember, the Value of each bit = 2^n, in this case, n is the order of the bit.
Bit 0 is worth 1 as 2^0 = 1,
Bit 3 is worth 8 as 2^3 = 8,
So to find out if the binary sequence above was actually 42, we would sum all of the bits:
(2^0)*0+
(2^1)*1+
(2^2)*0+
(2^3)*1+
(2^4)*0+
(2^5)*1+
(2^6)*0+
(2^7)*0+
Adding all those numbers together would leave you with 42.
Look at the calculation more carefully, read it from top to bottom and you will notice something: on the right side of the calculation, the sequenece is equal to 0010 1010
, which was our original number.
If you are converting from BinarySequence to lua, you could put 0b
as a prefix, like: 0b00101010
or you could use the simpler but worse way of tonumber(00101010,2)
or whatever your sequence is, it will return it in non-binary form (in this case: 42).
Remember: a byte’s value goes from 2^0 to 2^7 instead of 2^1 and 2^8 as the first value (bit 0) is worth 1, and the last value in a byte (bit 7) is worth 128.
If the Sequence was 1111 1111
, it would be 255; the size of a value in RGB.
A faster way to calculate how large of a number can fit in a binary sequence, just do (2^n)-1, in this case, n is the amount of bits in a sequence, which here is 8.
Binary Addition
Binary Addition is easier than it seems.
Lets say you want to add 15 to 23, first of all we need to get the Binary Sequence of each number
15 would be: 0000 1111
23 would be: 0001 0111
Just to explain it, lets add 1+1.
0000 0001
0000 0001
If both values are 1, then we would carry 1 to the next digit and put the current digit as 0.
0000 0001
0000 0001
________
0000 0010
Which of course would equal to: 2, which is correct.
So lets continue with our previous equation: 15 + 23.
0000 1111
0001 0111
On bit 0, both values are 1, so we will carry:
1
0000 1111
0001 0111
0000 0000
Here the carry and both values are 1. So we would carry 1 to the next bit and add the carry, which would also be 1 and a carry.
11
0000 1111
0001 0111
0000 1110
Lets speed up the process and get straight into the answer:
1 111
0000 1111
0001 0111
_________
0010 0110
The result is 0010 0110
, which is 38, which is 15+23.
Binary Subtraction
If you understood binary addition, this would be no challenge for you!
In Binary, there are many ways to define negative numbers, such as having the last bit be worth 0 - it’s original value, also known as opposite.
But I do not want to overcomplicate things and so for now, we will stick with non-negative integers.
Lets have 2 Values that we want to subtract from:
9 - 5, or in other words:
0000 1001
- 0000 0101
------------
Here it works differently,
If both numbers are 1, the output is 0.
If bottom number (the one we are subtracting the top number by) is 1 and top number is 0, it will carry to the next active bit. But instead of adding on to it, it would have that bit shifted right (halfing it)
By shifting to the right I simply mean this:
0010 1000 -> 0001 0100
But instead of the entire thing, we would just shift that bit.
Continueing with our example, lets do bit 0, which at this point you should know that it is the first bit.
0000 1001
0000 0101
In this case, it is basically 1-1, which is 0.
0000 1001
0000 0101
0000 0000
Bit 1 is empty on both sides so we will skip it.
Bit 2 is where it gets interesting, since the top number is false / 0, so we would search for the next bit,
We find our next active bit on Bit 3.
We shift Bit3 to Bit2, cancelling the 1 in the bottom sequence at bit2.
With it shifted, the calculation is more like this:
0000 0101
0000 0001
Apart from that we do not actually change the inputs, it is just to show the shift.
So that leaves us with:
0000 0101
0000 0001
0000 0100
0000 0100
equals to 4, which is 9-5.
Be Careful!
Once shifting a number, all numbers between the bit right of the bit being shifted and the bit that caused the shift would be activated!
In our case, the Bits were right next to each other, so only one was activated. But here is an example of one that would do that.
0000 1000
0000 0001
This is 8-1, but if we just shifted it to the right, then it would be
0000 0100
0000 0000
which would equal 4.
So instead we fill every bit in between The subtraction bit and The shifted bit’s new positon:
So it would be:
0000 0111
0000 0000
Which would equal to 7, which is our answer:
2 more examples:
0001 0010 -- 18
0000 0011 -- 3
-----------
0000 1111 -- 15
0010 0000 -- 32
0000 0001 -- 1
----------
0001 1111 -- 31
2 - Bit32 Library
In lua (The programming language that the Roblox Engine uses) has a Bit32 Library, that is useful for bitwise operations.
Here are the bitwise operators:
.band &
.bor |
.bxor ~
.bnot ~
.rshift >>
.lshift <<
instead of refering them as &
or |
, I will instead be using bit32.band
and bit32.bor
, to prevent any confussion.
As you can tell by the name, bit32 has 32 bits, not 8 bits that I used in the examples in Binary Arithmetic.
This allows the numbers to get crazy large, the limit is (2^32)-1
or 4294967295
, that is 4.29 Billion!
Bitwise Operators
AND
The AND operator (bit32.band)
returns the bits that are 1 in both A and B and returns it as an unsigned number.
Example:
local AND = bit32.band(2,3)
-- The 'AND' Variable would equal to 2, because:
-- 0010 (2)
-- 0011 (3)
-- they both have bit 1 as true, so the output would be 0010, which is 2.
OR
The OR operator (bit32.bor)
combines the bits of both inputs together and returns it as an unsigned number.
Example:
local OR = bit32.bor(14,17)
-- The 'OR' Variable would equal to 31, because:
-- 0000 1110 (14)
--- 0001 0001 (17)
-- if 1 or both of the values are 1, the output is 1.
XOR
The XOR operator (bit32.bxor)
is similar to OR, but if both bit values are 1, the output is 0, else if one of the bits are 1, then the output is one, if both bits are off, the output is off (0).
Example:
local XOR = bit32.bxor(12,9)
-- The 'XOR' Variable would be equal to 5, because:
-- 0000 1100(12)
-- 0000 1001 (9)
-- the output 0000 0101 (5) is because bit 3 are both 1, which goes to 0.
NOT
The NOT operator (bit32.bnot)
simply just inverts all the bits. It pretty much just returns (2^32)-(1+A), in this case A is the input given.
I do not think I need an example for this as it is pretty simple.
LEFT AND RIGHT SHIFT
Left and Right shift operators (bit32.lshift)
and (bit32.rshift)
simply shift the bits to the right or left.
Parameters:
bit32.lshift(A,B)
A: The integer that you want to shift.
B: How many bits to shift it by (if negative than it would shift the other way. bit32.lshift(2,-1)
== bit32.rshift(2,1)
.
BONUS: Extract and Rotate
Rotate
The right and left rotate operators (bit32.rrotate)
and (bit32.lrotate)
act the same as the shifts, but when a number is shifted out of the sequence, it goes to the other end (bit 31).
Extract
bit32.extract
has 3 parameters.
A: The number you want to extract the bts from.
B: The starting bit that you want to extract from.
C: How many bits from B to extract.
Example:
local E = bit32.extract(21984,0,8)
-- The 'E' Variable equals to: 224, because:
-- The bits for 21984 are: 00000000000000000101010111100000
-- The extract function started at bit 0, and did the next 8 bits (including bit 0)
-- So it just takes in 11100000, which is 224.
3 - Usage Examples
1. BitPacking
This would just be a simplified explanation of what BitPacking & BitMasking is. I strongly recommend this tutorial about it.
Packing process
Lets say you want to store 3 or 4 bytes of data into 1 number, such as for RGBs.
first of all, get your 3 or 4 integers between 0 and 255
local A = 254
local B = 89
local C = 13
local D = 12
If saving RGB, eithier leave D as blank or use it to hold a different value between 0 and 255.
Then We would shift B, C and D by 8 bits apart from each other.
local ShiftA = A
local ShiftB = bit32.lshift(B,8)
local ShiftC = bit32.lshift(C,16)
local ShiftD = bit32.lshift(D,24)
And then you would need to combine all these values into 1 number with:
local Total = bit32.bor(ShiftA,ShiftB,ShiftC,ShiftD)
Entire script:
local A = 254
local B = 89
local C = 13
local D = 12
local ShiftA = A
local ShiftB = bit32.lshift(B,8)
local ShiftC = bit32.lshift(C,16)
local ShiftD = bit32.lshift(D,24)
local Total = bit32.bor(ShiftA,ShiftB,ShiftC,ShiftD)
Unpacking process
The unpacking process is simple. We just extract the bits back into their original form.
local A = bit32.extract(Total,0,8)
local B = bit32.extract(Total,8,8)
local C = bit32.extract(Total,16,8)
local D = bit32.extract(Total,24,8)
A, B, C and D would be the same value from before they were packed.
2. String Inverser
This function inverses a string’s binary contents, acting as a very basic form of encription.
Output Example:
'Make sure to like the post!
→ ����ߌ���ߋ�ߓ���ߋ��ߏ����
����ߌ���ߋ�ߓ���ߋ��ߏ����
→ @B@B@B@B s@B@B@B t@B l@B@B@B t@B@B p@B@B@B@B
local function InverseString(String:string)
local Sequence = ''
for c = 1, #String do
local character = string.sub(String,c)
Sequence..=string.char(bit32.extract(bit32.bnot(string.byte(character)),0,8))
end
return Sequence
end
That’s it!
That is the tutorial! Remember to like if you found this helpful and reply if you are confused or got suggestions I should add.
Try out the SimpleBit Module that has some of these functions and much more!