Performing Bitwise Operators indirectly by using Arithmetic Operators and stuff

The code is in Go. It could be easily ported to other languages.

Did you know that you can bitshift a number to the left or right by multiplying a number by 2 or dividing(integer division) a number by 2 respectively?

Anyway, let’s start by getting the least significant bit of a number:

// SPDX-FileCopyrightText: 2024 iketsj <iketsj@gmail.com>
//
// SPDX-License-Identifier: MIT

func getLeastSignificantBit(operand uint32) uint32 {
	var operandLeastSignificantBit uint32
	//can also use modulo ( % 2)
	//shift the least significant bit to the left through multiplication
	//and ignore the consequent zeroes
	if operand * uint32(math.Pow(2, float64(31))) != 0 {
		operandLeastSignificantBit = 1
	} else {
		operandLeastSignificantBit = 0
	}
	return operandLeastSignificantBit
}

Then let’s move on how to perform bitwise AND.

AND Truth Table:
A | B| C
0 | 0| 0
0 | 1| 0
1 | 0| 0
1 | 1| 1
// SPDX-FileCopyrightText: 2024 iketsj <iketsj@gmail.com>
//
// SPDX-License-Identifier: MIT

//AND is performed using multiplication
func BitwiseAnd(operand1 uint32, operand2 uint32) uint32 {
	var answer uint32
	if operand1 == 0 || operand2 == 0 {
		//if one of the operand is already 0, 
		//anything multiplied(and) by 0 is automatically 0
		answer = 0
	} else {
		for i := 0; i <= 31; i++ {
			//get the least significant bit of both numbers
			operand1LeastSignificantBit := getLeastSignificantBit(operand1)
			operand2LeastSignificantBit := getLeastSignificantBit(operand2)
			//AND is performed using multiplication 
			//then shift it to the right through multiplication 
			//then add it to the already processed answer
			if operand1LeastSignificantBit * operand2LeastSignificantBit != 0 {
				answer += 1 * uint32(math.Pow(2, float64(i)))
			}
			//perform shift to the right by 1 using division by 2
			operand1 /= 2
			operand2 /= 2
			//if one of the operand is already 0, stop already
			if operand1 == 0 || operand2 == 0 {
				break
			}
		}
	}
	return answer
}

Then how to perform OR.

OR Truth Table:
A | B| C
0 | 0| 0
0 | 1| 1
1 | 0| 1
1 | 1| 1
// SPDX-FileCopyrightText: 2024 iketsj <iketsj@gmail.com>
//
// SPDX-License-Identifier: MIT

//OR is performed using addition but take care of carry 1 as 1 + 1 = 10 bin
func BitwiseOr(operand1 uint32, operand2 uint32) uint32 {
	var answer uint32
	if operand1 == 0 || operand2 == 0 {
		//if one of the operand is already 0, just add them
		answer = operand1 + operand2
	} else {
		for i := 0; i <= 31; i++ {
			//get the least significant bit of both numbers
			operand1LeastSignificantBit := getLeastSignificantBit(operand1)
			operand2LeastSignificantBit := getLeastSignificantBit(operand2)
			//perform OR operation by adding
			//this also takes care of the carry 1
			if operand1LeastSignificantBit + operand2LeastSignificantBit != 0 {
				answer += 1 * uint32(math.Pow(2, float64(i)))
			}
			//perform shift to the right by 1 using division by 2
			operand1 /= 2
			operand2 /= 2
			//if one of the operands is already 0, 
			//just add(or) the remaining bits to their proper place
			if operand1 == 0 {
				answer += operand2 * uint32(math.Pow(2, float64(i + 1)))
				break
			} else if operand2 == 0 {
				answer += operand1 * uint32(math.Pow(2, float64(i + 1)))
				break
			}
		}
	}
	return answer
}

Then how to perform XOR.

XOR Truth Table:
A | B| C
0 | 0| 0
0 | 1| 1
1 | 0| 1
1 | 1| 0
// SPDX-FileCopyrightText: 2024 iketsj <iketsj@gmail.com>
//
// SPDX-License-Identifier: MIT

//XOR, perform OR if bits are not the same. if bits are the same, answer is 1.
func BitwiseXor(operand1 uint32, operand2 uint32) uint32 {
	var answer uint32
	if operand1 == operand2 {
		//if both operands are equal, return 0
		answer = 0
	} else if operand1 == 0 || operand2 == 0 {
		//if one operand is 0, just add the other
		answer = operand1 + operand2
	} else {
		for i := 0; i <= 31; i++ {
			//get the least significant bit of both numbers
			operand1LeastSignificantBit := getLeastSignificantBit(operand1)
			operand2LeastSignificantBit := getLeastSignificantBit(operand2)
			//perform XOR operation
			//if bits are the same, bit answer is 0
			//if bits are not the same, bit answer is 1
			if operand1LeastSignificantBit != operand2LeastSignificantBit {
				answer += 1 * uint32(math.Pow(2, float64(i)))
			}
			//perform shift to the right by 1 using division by 2
			operand1 /= 2
			operand2 /= 2
			//if one of the operands is already 0
			//just add(OR) the remaining bits to their proper place
			//yes, OR
			if operand1 == 0 {
				answer += operand2 * uint32(math.Pow(2, float64(i + 1)))
				break
			} else if operand2 == 0 {
				answer += operand1 * uint32(math.Pow(2, float64(i + 1)))
				break
			}
		}
	}
	return answer
}

Then how to perform NOT.

NOT Truth Table:
A | C
0 | 1
1 | 0
// SPDX-FileCopyrightText: 2024 iketsj <iketsj@gmail.com>
//
// SPDX-License-Identifier: MIT

//NOT, just invert bits
func BitwiseNot(operand uint32) uint32 {
	var answer uint32
	if operand == 0 {
		//if operand is already 0
		//return the inverted bits which is 0xFFFFFFFF
		answer = 0xFFFFFFFF
	} else if operand == 0xFFFFFFFF {
		//if operand already has all the bits set
		//return the inverted bits which is 0x00000000
		answer = 0x00000000
	} else {
		for i := 0; i <= 31; i++ {
			//get the least significant bit of the number
			operandLeastSignificantBit := getLeastSignificantBit(operand)
			//perform NOT
			if operandLeastSignificantBit == 0 {
				answer += 1 * uint32(math.Pow(2, float64(i)))
			}
			//perform shift to the right by 1 using division by 2
			operand /= 2
			//if remaining bits are 0
			//trim 0xFFFFFFFF by shifting it to the right 
			//  by how many bits are already processed through division
			//shift it to the left by 
			//  how many bits are already processed through multiplication
			//  then add it to the processed answer
			if operand == 0 {
				answer += (0xFFFFFFFF / uint32(math.Pow(2, float64(i + 1)))) * uint32(math.Pow(2, float64(i + 1)))
				break
			}
		}
	}
	return answer
}

With NAND, XNOR, NOR. We can just chain together function calls.

BitwiseNot(BitwiseAnd(1, 1)) //4294967294 in unsigned 32bit
BitwiseNot(BitwiseXor(0, 0)) //4294967295 in unsigned 32bit
BitwiseNot(BitwiseOr(4, 4)) //4294967291 in unsigned 32bit

Note:
There might be some more optimized way.