8 booleans are stored in 1 boolean

Ferdi Tatlisu
Trendyol Tech
Published in
6 min readMar 20, 2023

Nowadays, disk size isn’t an issue but RAM size is really an issue.
Today, we are going to talk about bits and using them more efficiently.

When we start development, we don't know or don't care about variable types, you need 1 bit but you define the kind of 4 bytes variable of it. Because we just focus on writing code or finishing it. After a while, you can think to use the proper variable instead of a random usable variable. After a long journey, you start thinking can I use every bit of variable without any waste of bit?
If you started thinking about this question, you are right place.

BOOLEAN?

As I know, every programming language has the boolean variable type and it has 1 byte(8-bit). The Boolean has 2 values. True and False

True is 0000 0001
False is 0000 0000

If you read something about bits the first time, probably you say that "Why do we waste 7 bits every time" right now. As I told you are right place.

Anymore, every time we define a boolean, we waste 7 bits of everyone.

It's not a big deal, as 7 bits is a really small size for our domain data.

Next step: Size of JSON

In high probability, you use JSON as a text format in your data store or in network traffic.

Let's imagine, you have the struct of your domain.

type Data struct 
isTrue bool
isClass bool
isLive bool
isIron bool
isSearchable bool
isCode bool
isFlag bool
isFunc bool
}
{
"isTrue": false,
"isClass": false,
"isLive": true,
"isIron": false,
"isSearchable": false,
"isCode": false,
"isFlag": true,
"isFunc": false
}

If you store your data as JSON and imagine this is your data. Your document is 175 bytes. Because JSON stores all data as text.

If, we store booleans as bit. What does it look like?

type Data struct 
Value uint8 //uint8 is 1 bytes, int64 is 8 bytes
}
{
"value": 68
}

The document size is 19 bytes. From 175 bytes to 19 bytes. It's getting interesting. I think it's a huge size if you have a billion documents.

If, you are interested in it. Let's come to the point!

We have one uint8 variable in the struct. uint8 is a 1-byte variable. 1-byte means 0000 0000.

type Data struct {
Value uint8 // (uint8 == 1 byte)
// isTrue bool
// isClass bool
// isLive bool
// isIron bool
// isSearchable bool
// isCode bool
// isFlag bool
// isFunc bool
}

Let's assign every boolean to every bit of 1-byte.

Look at the above image. Every boolean is assigned to 1 bit. 0 means false, and 1 means true. So isFlag and isLive booleans are true and others false.
0100 0100 means 68.

68(0100 0100) How do we calculate binary to decimal?

A binary number is a number expressed in the base-2 numeral system. Calculate as below.

More examples of base-2:

  • 0000 0001 = 1
  • 0000 0100 = 4
  • 1000 0000 = 128
  • 1111 1111 = 255

Before coding, learn the operators

Before understanding how it works, we have to know |, &, and >> the operators.

Or ( | ) operator:

The | (bitwise inclusive OR) operator compares the values (in binary format) of each operand and yields a value whose bit pattern shows which bits in either of the operands has the value 1. If both of the bits are 0, the result of that bit is 0; otherwise, the result is 1.

  • 0 | 1 = 1
  • 1 | 1 = 1
  • 0 | 0 = 0

And (&) operator:

The & (bitwise AND) operator compares each bit of its first operand to the corresponding bit of the second operand. If both bits are 1's, the corresponding bit of the result is set to 1. Otherwise, it sets the corresponding result bit to 0.

  • 1 & 1 = 1
  • 0 & 1 = 0
  • 0 & 0 = 0

Shift (>>) operator:

The bitwise shift operators move the bit values of a binary object. Indicates the bits are to be shifted to the left.

  • 0100 >> 1(1 time shift to the left) = 0010
  • 0100 >> 2 = 0001
  • 0100 >> 3 = 0000

Time to coding

type Data struct {
Value uint8 // (uint8 == 1 byte)
// isTrue bool //0 => math.Pow(2, 0) => 1
// isClass bool //1 => math.Pow(2, 1) => 2
// isLive bool //2 => math.Pow(2, 2) => 4
// isIron bool //3 => math.Pow(2, 3) => 8
// isSearchable bool //4 => math.Pow(2, 4) => 16
// isCode bool //5 => math.Pow(2, 5) => 32
// isFlag bool //6 => math.Pow(2, 6) => 64
// isFunc bool //7 => math.Pow(2, 7) => 128
}

Our domain data will be as above. Every boolean field has a unique decimal number and they have been multiples of 2.

Let's create of getter and setter of the isIron boolean field

Setter

func (d *Data) setIsIron(isIron bool) {
if isIron {
d.Value |= 8
}else{
d.Value &= (255 - 8)
}
}

I created a setter function basically. Let's imagine that the value is 33 and we invoke the setIsIron function with either true or false parameters.

  • 0010 0001 == 33
  • 0000 1000 == 7
  • 1111 0111 == 247

These are numbers and let's calculate with bitwise operators.
33 means isIron is false, let's change it to true

  • 0010 0001(33) | 0000 1000(8) = 0010 1001(41)

41 means isIron is true, let's change it to false

  • 0010 1001(41) & 1111 0111(247) = 0010 0001(33)

Getter

func (d *Data) getIsIron() bool {
v := d.Value >> 3
return v&1 == 1
}

0000 0000 let's imagine that it's an array and has 8 elements. isIron value at the 3rd index of the array. We need to get 3rd element of the array. If we shift the bits to the left 3 times. Our bit will be left.
Let's see an example: 0010 1001(41) and the isIron value is true.

  • if shifted 1 time 0010 1001 >> 1 = 0001 0100
  • if shifted one more time 0001 0100 >> 1 = 0000 1010
  • if shifted one more time 0000 1010 >> 1 = 0000 0101(5)

so convert to getter function to binary code:

0010 1001(41) >> 3 = 0000 0101(5)
0000 0101(5) & 0000 0001(1) = 0000 0001

If the result equals 1 then the value is true, else false. Here value is 1 and the result is true.

Calculate in a real scenario

{
"id": "00000001f1edc56356638b741c9b6c70",
"merchantId": 11111,
"itemNumber": 222222222,
"fulfilmentType": "mp",
"isTR": false,
"sellerBarcode": "00000002600000",
"type": "Listing",
"version": 2,
"isSaleable": true,
"createdBy": "abc@abc.com",
"createdOn": 1634569309.5360124,
"lastModifiedBy": "Random user",
"lastModifiedOn": 1670574499.1526103,
"hasLimitedSaleReason": false,
"maxSaleQuantity": 10,
"isOnHold": false,
"maxSaleQuantityLimit": {
"lower": 10,
"upper": 10
},
"deliveryOptions": {
"isRushDelivery": false,
"fastDeliveryOptions": []
},
"tags": [{
"name": "tag-name",
"isActive": true
}],
"domainEvents": [],
"hasGroupDeal":
"isDeleted": false
}

The above doc size is 802 bytes. If we apply stores all booleans at 1 byte, our document will be 613 bytes. Every document earns 189 bytes.
Approximately %25 doc size shrink. I think it's huge, what do you think?

If you’re interested in joining our team, you can apply for the backend developer role or any of our current open positions.

Codes that are used in examples: https://github.com/ferditatlisu/8-booleans-in-1-boolean

Thank you for reading until here. See you next in my article.

THE END

--

--