This tutorial explains how computers represent numbers in binary, and how you can manipulate individual bits using bitwise operators. Understanding these concepts will help you write more efficient code and understand how low-level systems work.
In modern development, these 'low-level' operations are often tucked away behind abstractions and higher-level APIs. It's not surprising that many programmers, even seasoned ones, aren't familiar with bitwise operations. When Mark Sibly was introducing collisions in his last project (SGDLib) and using flags in collisions, it became apparent just how many developers hadn't encountered these concepts before.
However, bitwise operations are handy to know and will go a long way in understanding how software works at a fundamental level. In future, I may implement features in BambooBasic that require the use of bitwise operations for setting and receiving flags and other such things, so this knowledge will become essential.
Computers store all data as binary - sequences of 0s and 1s called bits. Unlike our decimal system (base 10) which uses digits 0-9, binary (base 2) only uses 0 and 1.
A byte is 8 bits (half a byte is a nibble). For example, the number 42 in binary is 00101010.
Each position in a binary number represents a power of 2:
| Position | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| Power of 2 | 2^7 | 2^6 | 2^5 | 2^4 | 2^3 | 2^2 | 2^1 | 2^0 |
| Decimal Value | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
| Binary (42) | 0 | 0 | 1 | 0 | 1 | 0 | 1 | 0 |
| Calculation: 0 + 0 + 32 + 0 + 8 + 0 + 2 + 0 = 42 | ||||||||
Converting decimal to binary:
Example: Convert 42 to binary 42 / 2 = 21 remainder 0 <-- Least significant bit (rightmost) 21 / 2 = 10 remainder 1 10 / 2 = 5 remainder 0 5 / 2 = 2 remainder 1 2 / 2 = 1 remainder 0 1 / 2 = 0 remainder 1 <-- Most significant bit (leftmost) Read upwards: 101010 = 42
Common power-of-2 values to memorize:
| Power | Value | Notes |
| 2^0 | 1 | |
| 2^1 | 2 | |
| 2^2 | 4 | |
| 2^3 | 8 | |
| 2^4 | 16 | |
| 2^5 | 32 | |
| 2^6 | 64 | |
| 2^7 | 128 | |
| 2^8 | 256 | 1 byte can hold 0-255 |
| 2^9 | 512 | |
| 2^10 | 1,024 | 1 Kilobyte (KB) |
| 2^16 | 65,536 | 64 KB |
| 2^20 | 1,048,576 | 1 Megabyte (MB) |
Binary numbers get long quickly. Hexadecimal (base 16) is a shorthand that uses digits 0-9 and letters A-F.
Each hex digit represents exactly 4 bits:
| Decimal | Binary | Hex |
| 0 | 0000 | 0 |
| 1 | 0001 | 1 |
| 2 | 0010 | 2 |
| 3 | 0011 | 3 |
| 4 | 0100 | 4 |
| 5 | 0101 | 5 |
| 6 | 0110 | 6 |
| 7 | 0111 | 7 |
| 8 | 1000 | 8 |
| 9 | 1001 | 9 |
| 10 | 1010 | A |
| 11 | 1011 | B |
| 12 | 1100 | C |
| 13 | 1101 | D |
| 14 | 1110 | E |
| 15 | 1111 | F |
Example: The number 255 in different bases:
Decimal: 255 Binary: 11111111 Hexadecimal: $FF
In BambooBasic, use $ prefix for hex: Local color:Int = $FF0000
The BitAnd operator compares each bit. The result bit is 1 only if both input bits are 1.
Truth Table:
A | B | A BitAnd B --|---|------------ 0 | 0 | 0 0 | 1 | 0 1 | 0 | 0 1 | 1 | 1
Example:
00101010 (42 in binary) BitAnd 00001111 (15 in binary - a "mask") -------- 00001010 (10 in binary)
Code Example:
Local a:Int = 42 Local mask:Int = 15 Local result:Int = a BitAnd mask Print result ; Output: 10 ; Common use: Extract lower 4 bits Local value:Int = $AB Local lowerNibble:Int = value BitAnd $0F Print lowerNibble ; Output: 11 (B in hex = 11)
Practical Use - Testing Flags:
Const FLAG_VISIBLE:Int = 1 ; Binary: 00000001
Const FLAG_SOLID:Int = 2 ; Binary: 00000010
Const FLAG_ANIMATED:Int = 4 ; Binary: 00000100
Local objectFlags:Int = FLAG_VISIBLE BitOr FLAG_SOLID ; Binary: 00000011
; Check if object is visible
If (objectFlags BitAnd FLAG_VISIBLE) <> 0 Then
Print "Object is visible"
EndIf
; Check if object is solid
If (objectFlags BitAnd FLAG_SOLID) <> 0 Then
Print "Object is solid"
EndIf
; Check if object is animated
If (objectFlags BitAnd FLAG_ANIMATED) <> 0 Then
Print "Object is NOT animated"
EndIf
The BitOr (or &) operator compares each bit. The result bit is 1 if either or both input bits are 1.
Truth Table:
A | B | A BitOr B --|---|---------- 0 | 0 | 0 0 | 1 | 1 1 | 0 | 1 1 | 1 | 1
Example:
00101010 (42) BitOr 00001111 (15) -------- 00101111 (47)
Code Example:
Local a:Int = 42 Local b:Int = 15 Local result:Int = a BitOr b ; Or using the & symbol: Local result2:Int = a & b Print result ; Output: 47
Practical Use - Combining Flags:
; Windows API style - combining multiple window styles Const WS_VISIBLE:Int = $10000000 Const WS_CHILD:Int = $40000000 Const WS_BORDER:Int = $00800000 ; Combine multiple flags into one value Local windowStyle:Int = WS_VISIBLE BitOr WS_CHILD BitOr WS_BORDER ; Or with & symbol: Local windowStyle2:Int = WS_VISIBLE & WS_CHILD & WS_BORDER ; This is how BGI functions work internally: ; CreateWindow(..., windowStyle)
Setting Individual Bits:
Local flags:Int = 0 ; Start with all bits off: 00000000 flags = flags BitOr 1 ; Turn on bit 0: 00000001 flags = flags BitOr 4 ; Turn on bit 2: 00000101 Print flags ; Output: 5
The BitXor operator compares each bit. The result bit is 1 if the bits are different.
Truth Table:
A | B | A BitXor B --|---|------------ 0 | 0 | 0 0 | 1 | 1 1 | 0 | 1 1 | 1 | 0
Example:
00101010 (42) BitXor 00001111 (15) -------- 00100101 (37)
Practical Use - Toggling Flags:
Const FLAG_MUTE:Int = 1 Local audioSettings:Int = 0 ; Sound is on ; Toggle mute on audioSettings = audioSettings BitXor FLAG_MUTE Print audioSettings ; Output: 1 (muted) ; Toggle mute off again audioSettings = audioSettings BitXor FLAG_MUTE Print audioSettings ; Output: 0 (unmuted)
XOR Swap Trick (for fun):
; Swap two variables without a temp variable Local a:Int = 42 Local b:Int = 15 a = a BitXor b b = a BitXor b ; b now contains original a a = a BitXor b ; a now contains original b Print a ; Output: 15 Print b ; Output: 42
The BitNot operator flips all bits: 0 becomes 1, and 1 becomes 0.
Example (8-bit):
BitNot 00101010 = 11010101 BitNot 42 = -43 (in signed integers)
Code Example:
Local value:Int = 42 Local inverted:Int = BitNot value Print inverted ; Output will be negative due to two's complement
Practical Use - Clearing Specific Bits:
Const FLAG_VISIBLE:Int = 1 Const FLAG_SOLID:Int = 2 Local flags:Int = FLAG_VISIBLE BitOr FLAG_SOLID ; Both flags set ; Clear the SOLID flag (turn it off) flags = flags BitAnd (BitNot FLAG_SOLID) ; Result: Only VISIBLE flag remains set Print flags ; Output: 1
Shift Left (Shl) - Moves bits to the left, filling with zeros on the right.
00000101 Shl 1 = 00001010 (5 shifted left by 1 = 10) 00000101 Shl 2 = 00010100 (5 shifted left by 2 = 20)
Effect: Each left shift multiplies by 2.
Local value:Int = 5 Print value Shl 1 ; Output: 10 (5 * 2) Print value Shl 2 ; Output: 20 (5 * 4) Print value Shl 3 ; Output: 40 (5 * 8)
Shift Right (Shr) - Moves bits to the right, filling with zeros on the left.
00101000 Shr 1 = 00010100 (40 shifted right by 1 = 20) 00101000 Shr 2 = 00001010 (40 shifted right by 2 = 10)
Effect: Each right shift divides by 2 (integer division).
Local value:Int = 40 Print value Shr 1 ; Output: 20 (40 / 2) Print value Shr 2 ; Output: 10 (40 / 4) Print value Shr 3 ; Output: 5 (40 / 8)
Performance Note: Bit shifts are much faster than multiplication/division, though modern compilers often optimize this automatically.
One of the most common uses of bitwise operations is managing multiple boolean flags in a single integer.
Traditional Approach (wasteful):
; Uses 4 bytes minimum (one Int per flag) Local isVisible:Int = True Local isSolid:Int = True Local isAnimated:Int = False Local canMove:Int = True
Bitwise Approach (efficient):
; Define flags as powers of 2 (each uses one bit)
Const FLAG_VISIBLE:Int = 1 ; Bit 0: 00000001
Const FLAG_SOLID:Int = 2 ; Bit 1: 00000010
Const FLAG_ANIMATED:Int = 4 ; Bit 2: 00000100
Const FLAG_CAN_MOVE:Int = 8 ; Bit 3: 00001000
; Store all flags in one integer (uses only 4 bytes total)
Local objectFlags:Int = 0
; Set flags (turn bits on)
objectFlags = FLAG_VISIBLE BitOr FLAG_SOLID BitOr FLAG_CAN_MOVE
; Result: 00001011 (bits 0, 1, and 3 are on)
; Check if a flag is set
If (objectFlags BitAnd FLAG_VISIBLE) <> 0 Then
Print "Object is visible"
EndIf
; Toggle a flag
objectFlags = objectFlags BitXor FLAG_ANIMATED ; Turn on animation
objectFlags = objectFlags BitXor FLAG_ANIMATED ; Turn off animation
; Clear a specific flag (turn bit off)
objectFlags = objectFlags BitAnd (BitNot FLAG_SOLID) ; Remove solid flag
; Check multiple flags at once
If (objectFlags BitAnd (FLAG_VISIBLE BitOr FLAG_CAN_MOVE)) <> 0 Then
Print "Object is visible OR can move"
EndIf
Real-World Example - File Permissions:
; Unix-style file permissions
Const PERM_READ:Int = 4 ; 100 in binary
Const PERM_WRITE:Int = 2 ; 010 in binary
Const PERM_EXECUTE:Int = 1 ; 001 in binary
; Read + Write permission (110 in binary = 6)
Local filePerms:Int = PERM_READ BitOr PERM_WRITE
; Check if file is readable
If (filePerms BitAnd PERM_READ) <> 0 Then
Print "File is readable"
EndIf
RGB colors are often stored as a single 32-bit integer, with each color component occupying 8 bits.
Color Format: $AARRGGBB
Bits 24-31: Alpha (transparency) Bits 16-23: Red Bits 8-15: Green Bits 0-7: Blue
Creating a Color from Components:
Function MakeColor:Int(red:Int, green:Int, blue:Int, alpha:Int)
; Shift each component into position and combine with OR
Local color:Int = (alpha Shl 24) BitOr (red Shl 16) BitOr (green Shl 8) BitOr blue
Return color
EndFunction
; Example: Create opaque red color (255, 0, 0, 255)
Local redColor:Int = MakeColor(255, 0, 0, 255)
Print "Color: $" + Hex$(redColor) ; Output: Color: $FF0000FF
Extracting Color Components:
Function GetRed:Int(color:Int)
Return (color Shr 16) BitAnd $FF ; Shift red to position 0-7, mask other bits
EndFunction
Function GetGreen:Int(color:Int)
Return (color Shr 8) BitAnd $FF ; Shift green to position 0-7, mask other bits
EndFunction
Function GetBlue:Int(color:Int)
Return color BitAnd $FF ; Blue is already in position 0-7, just mask
EndFunction
Function GetAlpha:Int(color:Int)
Return (color Shr 24) BitAnd $FF ; Shift alpha to position 0-7, mask other bits
EndFunction
; Example: Extract components from a color
Local color:Int = $FF8844AA
Print "Red: " + GetRed(color) ; Output: Red: 136
Print "Green: " + GetGreen(color) ; Output: Green: 68
Print "Blue: " + GetBlue(color) ; Output: Blue: 170
Print "Alpha: " + GetAlpha(color) ; Output: Alpha: 255
Color Manipulation:
; Darken a color by reducing all components
Function DarkenColor:Int(color:Int, amount:Int)
Local r:Int = Max(0, GetRed(color) - amount)
Local g:Int = Max(0, GetGreen(color) - amount)
Local b:Int = Max(0, GetBlue(color) - amount)
Local a:Int = GetAlpha(color)
Return MakeColor(r, g, b, a)
EndFunction
; Set alpha (transparency) of a color
Function SetAlpha:Int(color:Int, newAlpha:Int)
; Clear alpha bits, then OR in new alpha
color = color BitAnd $00FFFFFF ; Clear alpha (keep RGB)
color = color BitOr (newAlpha Shl 24)
Return color
EndFunction
Bit masking is used to isolate specific bits from a larger value.
Example: Parsing Network Packet Header
; Packet header format (16 bits):
; Bits 0-3: Protocol version (4 bits)
; Bits 4-7: Message type (4 bits)
; Bits 8-15: Sequence number (8 bits)
Function ParsePacketHeader(header:Int)
; Extract version (bits 0-3)
Local version:Int = header BitAnd $000F
; Extract message type (bits 4-7)
Local msgType:Int = (header Shr 4) BitAnd $000F
; Extract sequence number (bits 8-15)
Local seqNum:Int = (header Shr 8) BitAnd $00FF
Print "Version: " + version
Print "Message Type: " + msgType
Print "Sequence: " + seqNum
EndFunction
; Example packet: $4523
; Binary: 0100 0101 0010 0011
; ^^^^ ^^^^ ^^^^ ^^^^
; Seq Seq Type Ver
ParsePacketHeader($4523)
; Output:
; Version: 3 (0011)
; Message Type: 2 (0010)
; Sequence: 69 (01000101)
You can pack multiple small values into a single integer to save memory.
Example: Packing Player Stats
; Pack 4 values into one 32-bit integer:
; Bits 0-7: Health (0-255)
; Bits 8-15: Mana (0-255)
; Bits 16-23: Level (0-255)
; Bits 24-31: Class (0-255)
Function PackPlayerStats:Int(health:Int, mana:Int, level:Int, class:Int)
Local packed:Int = health BitOr (mana Shl 8) BitOr (level Shl 16) BitOr (class Shl 24)
Return packed
EndFunction
Function UnpackHealth:Int(packed:Int)
Return packed BitAnd $FF
EndFunction
Function UnpackMana:Int(packed:Int)
Return (packed Shr 8) BitAnd $FF
EndFunction
Function UnpackLevel:Int(packed:Int)
Return (packed Shr 16) BitAnd $FF
EndFunction
Function UnpackClass:Int(packed:Int)
Return (packed Shr 24) BitAnd $FF
EndFunction
; Example usage
Local stats:Int = PackPlayerStats(100, 50, 12, 3)
Print "Health: " + UnpackHealth(stats) ; Output: Health: 100
Print "Mana: " + UnpackMana(stats) ; Output: Mana: 50
Print "Level: " + UnpackLevel(stats) ; Output: Level: 12
Print "Class: " + UnpackClass(stats) ; Output: Class: 3
The Windows API extensively uses bitwise flags. Understanding this is crucial for BGI and native Windows programming.
Window Styles Example:
; Common Windows API flags (from winuser.h)
Const WS_OVERLAPPED:Int = $00000000
Const WS_CAPTION:Int = $00C00000
Const WS_SYSMENU:Int = $00080000
Const WS_THICKFRAME:Int = $00040000
Const WS_MINIMIZEBOX:Int = $00020000
Const WS_MAXIMIZEBOX:Int = $00010000
Const WS_VISIBLE:Int = $10000000
; Combine multiple styles
Const WS_OVERLAPPEDWINDOW:Int = WS_OVERLAPPED BitOr WS_CAPTION BitOr _
WS_SYSMENU BitOr WS_THICKFRAME BitOr _
WS_MINIMIZEBOX BitOr WS_MAXIMIZEBOX
; Create window with multiple styles
Local style:Int = WS_OVERLAPPEDWINDOW BitOr WS_VISIBLE
BGI Message Box Example:
; MessageBox button types
Const MB_OK:Int = $00000000
Const MB_OKCANCEL:Int = $00000001
Const MB_YESNO:Int = $00000004
; MessageBox icons
Const MB_ICONINFORMATION:Int = $00000040
Const MB_ICONWARNING:Int = $00000030
Const MB_ICONERROR:Int = $00000010
; Combine button type and icon
Local flags:Int = MB_YESNO BitOr MB_ICONWARNING
Local result:Int = BGI_MessageBox("Save changes?", "Confirm", flags, 0, window)
File Access Modes:
; File access flags
Const FILE_READ:Int = 1
Const FILE_WRITE:Int = 2
Const FILE_CREATE:Int = 4
Const FILE_APPEND:Int = 8
; Open file for reading and writing
Local mode:Int = FILE_READ BitOr FILE_WRITE
; Check if file can be written to
If (mode BitAnd FILE_WRITE) <> 0 Then
Print "File is writable"
EndIf
Bitwise operations are among the fastest operations a CPU can perform because they work directly with the binary representation of data.
Fast Multiplication/Division by Powers of 2:
; Slow (uses CPU multiply instruction) Local result:Int = value * 16 ; Fast (bit shift) Local result:Int = value Shl 4 ; Same as * 16 ; Slow (uses CPU divide instruction) Local result:Int = value / 8 ; Fast (bit shift) Local result:Int = value Shr 3 ; Same as / 8
Memory Efficiency:
; Store 32 boolean flags in one integer instead of 32 separate variables Local flags:Int = 0 ; Instead of: Local flag0, flag1, flag2... flag31:Int
When to Use Bitwise Operations:
When NOT to Use Bitwise Operations:
Bitwise operations are a fundamental tool in a programmer's toolkit. They enable:
While modern high-level programming often abstracts these details away, understanding bitwise operations makes you a more capable programmer and is essential for systems programming, game development, and working with APIs like POSIX and Vulkan.
BambooBasic © 2026 Michael Denathorn