Sunday, December 4, 2022
HomeTechnologyPractical guide to binary operations using the UInt8 type in Swift

Practical guide to binary operations using the UInt8 type in Swift



Representing numbers as integers

Now that we know what kind of integers are available in Swift, it’s time to talk a bit about what kind of numbers can we represent using these data types.



print(Int.min)      
print(Int.max)      
print(UInt.min)     
print(UInt.max)     
print(UInt8.min)    
print(UInt8.max)    
print(UInt16.min)   
print(UInt16.max)   
print(UInt32.min)   
print(UInt32.max)   
print(UInt64.min)   
print(UInt64.max)   
print(Int8.min)     
print(Int8.max)     
print(Int16.min)    
print(Int16.max)    
print(Int32.min)    
print(Int32.max)    
print(Int64.min)    
print(Int64.max)    


So there is a minimum and maximum value for each integer type that we can store in a given variable. For example, we can’t store the value 69420 inside a UInt8 type, because there are simply not enough bits to represent this huge number. 🤓


Let’s examine our 8 bit long unsigned integer type. 8 bit means that we have literally 8 places to store boolean values (ones and zeros) using the binary number representation. 0101 0110 in binary is 86 using the “regular” decimal number format. This binary number is a base-2 numerical system (a positional notation) with a radix of 2. The number 86 can be interpreted as:


  • 0*28+1*27+0*26+1*25+0*24 + 1*23+1*22+0*21+0*20
  • 0*128+1*64+0*32+1*16 + 0*8+1*4+1*2+0*1
  • 64+16+4+2
  • 86


We can convert back and forth between decimal and binary numbers, it’s not that hard at all, but let’s come back to this topic later on. In Swift we can check if a type is a signed type and we can also get the length of the integer type through the bitWidth property.


print(Int.isSigned)     
print(UInt.isSigned)    
print(Int.bitWidth)     
print(UInt8.bitWidth)   


Based on this logic, now it’s quite straightforward that an 8 bit long unsigned type can only store 255 as the maximum value (1111 1111), since that’s 128+64+32+16+8+4+2+1.


What about signed types? Well, the trick is that 1 bit from the 8 is reserved for the positive / negative symbol. Usually the first bit represents the sign and the remaining 7 bits can store the actual numeric values. For example the Int8 type can store numbers from -128 til 127, since the maximum positive value is represented as 0111 1111, 64+32+16+8+4+2+1, where the leading zero indicates that we’re talking about a positive number and the remaining 7 bits are all ones.


So how the hack can we represent -128? Isn’t -127 (1111 1111) the minimum negative value? 😅


Nope, that’s not how negative binary numbers work. In order to understand negative integer representation using binary numbers, first we have to introduce a new term called two’s complement, which is a simple method of signed number representation.


Basic signed number maths


It is relatively easy to add two binary numbers, you just add the bits in order with a carry, just like you’d do addition using decimal numbers. Subtraction on the other hand is a bit harder, but fortunately it can be replaced with an addition operation if we store negative numbers in a special way and this is where two’s complement comes in.


Let’s imagine that we’d like to add two numbers:


  • 0010 1010 (+42)
  • 0100 0101 +(+69)
  • 0110 1111 =(+111)


Now let’s add a positive and a negative number stored using two’s complement, first we need to express -6 using a signed 8 bit binary number format:


  • 0000 0110 (+6)
  • 1111 1001 (one’s complement = inverted bits)
  • 1111 1010 (two’s complenet = add +1 (0000 0001) to one’s complement)


Now we can simply perform an addition operation on the positive and negative numbers.


  • 0010 1010 (+42)
  • 1111 1010 +(-6)
  • (1) 0010 0100 =(+36)


So, you might think, what’s the deal with the extra 1 in the beginning of the 8 bit result? Well, that’s called a carry bit, and in our case it won’t affect our final result, since we’ve performed a subtraction instead of an addition. As you can see the remaining 8 bit represents the positive number 36 and 42-6 is exactly 36, we can simply ignore the extra flag for now. 😅


Binary operators in Swift

Enough from the theory, let’s dive in with some real world examples using the UInt8 type. First of all, we should talk about bitwise operators in Swift. In my previous article we’ve talked about Bool operators (AND, OR, NOT) and the Boolean algebra, now we can say that those functions operate using a single bit. This time we’re going to see how bitwise operators can perform various transformations using multiple bits. In our sample cases it’s always going to be 8 bit. 🤓


Bitwise NOT operator

This operator (~) inverts all bits in a number. We can use it to create one’s complement values.



let x: UInt8 = 0b00000110    
let res = ~x                 
print(res)                   
print(String(res, radix: 2)) 


Well, the problem is that we’ll keep seeing decimal numbers all the time when using int types in Swift. We can print out the correct 1111 1001 result, using a String value with the base of 2, but for some reason the inverted number represents 249 according to our debug console. 🙃


This is because the meaning of the UInt8 type has no understanding about the sign bit, and the 8th bit is always refers to the 28 value. Still, in some cases e.g. when you do low level programming, such as building a NES emulator written in Swift, this is the right data type to choose.

The Data type from the Foundation framework is considered to be a collection of UInt8 numbers. Actually you’ll find quite a lot of use-cases for the UInt8 type if you take a deeper look at the existing frameworks & libraries. Cryptography, data transfers, etc.


Anyway, you can make an extension to easily print out the binary representation for any unsigned 8 bit number with leading zeros if needed. 0️⃣0️⃣0️⃣0️⃣ 0️⃣1️⃣1️⃣0️⃣



import Foundation

fileprivate extension String {
    
    func leftPad(with character: Character, length: UInt) -> String {
        let maxLength = Int(length) - count
        guard maxLength > 0 else {
            return self
        }
        return String(repeating: String(character), count: maxLength) + self
    }
}

extension UInt8 {
    var bin: String {
        String(self, radix: 2).leftPad(with: "0", length: 8)
    }
}

let x: UInt8 = 0b00000110   
print(String(x, radix: 2))  
print(x.bin)                
print((~x).bin)             
let res = (~x) + 1          
print(res.bin)


We still have to provide our custom logic if we want to express signed numbers using UInt8, but that’s only going to happen after we know more about the other bitwise operators.


Bitwise AND, OR, XOR operators

These operators works just like you’d expect it from the truth tables. The AND operator returns a one if both the bits were true, the OR operator returns a 1 if either of the bits were true and the XOR operator only returns a true value if only one of the bits were true.


  • AND & – 1 if both bits were 1
  • OR | – 1 if either of the bits were 1
  • XOR ^ – 1 if only one of the bits were 1


Let me show you a quick example for each operator in Swift.


let x: UInt8 = 42   
let y: UInt8 = 28   
print((x & y).bin)  
print((x | y).bin)  
print((x ^ y).bin)  


Mathematically speaking, there is not much reason to perform these operations, it won’t give you a sum of the numbers or other basic calculation results, but they have a different purpose.

You can use the bitwise AND operator to extract bits from a given number. For example if you want to store 8 (or less) individual true or false values using a single UInt8 type you can use a bitmask to extract & set given parts of the number. 😷


var statusFlags: UInt8 = 0b00000100


print(statusFlags & 0b00000100 == 4)   
print(statusFlags & 0b00010000 == 16)  
statusFlags = statusFlags & 0b11101111 | 16
print(statusFlags.bin)  
statusFlags = statusFlags & 0b11111011 | 0
print(statusFlags.bin) 
statusFlags = statusFlags & 0b11101111 | 0
print(statusFlags.bin) 
statusFlags = statusFlags & 0b11101011 | 4
print(statusFlags.bin) 


This is nice, especially if you don’t want to mess around with 8 different Bool variables, but one there is one thing that is very inconvenient about this solution. We always have to use the right power of two, of course we could use pow, but there is a more elegant solution for this issue.


Bitwise left & right shift operators

By using a bitwise shift operation you can move a bit in a given number to left or right. Left shift is essentially a multiplication operation and right shift is identical with a division by a factor of two.


“Shifting an integer’s bits to the left by one position doubles its value, whereas shifting it to the right by one position halves its value.” – swift.org


It’s quite simple, but let me show you a few practical examples so you’ll understand it in a bit. 😅


let meaningOfLife: UInt8 = 42



print(meaningOfLife << 1) 
print(meaningOfLife << 2) 
print(meaningOfLife << 3) 
print(meaningOfLife >> 1) 
print(meaningOfLife >> 2) 
print(meaningOfLife >> 3) 
print(meaningOfLife >> 4) 
print(meaningOfLife >> 5) 
print(meaningOfLife >> 6) 
print(meaningOfLife >> 7) 


As you can see we have to be careful with left shift operations, since the result can overflow the 8 bit range. If this happens, the extra bit will just go away and the remaining bits are going to be used as a final result. Right shifting is always going to end up as a zero value. ⚠️


Now back to our status flag example, we can use bit shifts, to make it more simple.


var statusFlags: UInt8 = 0b00000100


print(statusFlags & 1 << 2 == 1 << 2)


statusFlags = statusFlags & ~(1 << 2) | 0
print(statusFlags.bin)


statusFlags = statusFlags & ~(1 << 2) | 1 << 2
print(statusFlags.bin)


As you can see we’ve used quite a lot of bitwise operations here. For the first check we use left shift to create our mask, bitwise and to extract the value using the mask and finally left shift again to compare it with the underlying value. Inside the second set operation we use left shift to create a mask then we use the not operator to invert the bits, since we’re going to set the value using a bitwise or function. I suppose you can figure out the last line based on this info, but if not just practice these operators, they are very nice to use once you know all the little the details. ☺️


I think I’m going to cut it here, and I’ll make just another post about overflows, carry bits and various transformations, maybe we’ll involve hex numbers as well, anyway don’t want to promise anything specific. Bitwise operations are usueful and fun, just practice & don’t be afraid of a bit of math. 👾

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments