Bytes for Beginners: Build your own Conversions from Int and Double to Binary (and Other Number Bases) in Swift (update: Xcode 8, beta 6; Swift 3)

Why convert a decimal to a binary yourself when Swift can do it for you?
String(500, radix:2)
Well it helps in learning:
extension Int {
    func binary() -> String {
        var num = self
        var str = ""
        repeat {
            str = String(num%2) + str
            num = num/2
            }
            while num > 0
        return str
    }
}
500.binary()
And as you can see the process of going from base 10 to base 2 is a straightforward exercise. We simply keep dividing by the base number and adding the remainder to the beginning of our string. In fact going from base 10 to any base from 2 through to 9 is straightforward, because we can do exactly the same thing for those number bases too.
extension Int {
    func toBase(b:Int) -> String {
        guard b > 1 && b < 11 else {
            fatalError("base too high")
        }
        var num = self
        var str = ""
        repeat {
            str = String(num%b) + str
            num = num/b
        }
            while num > 0
        return str
    }
}

500.toBase(5) == String(500, radix:5) // true
We can even handle bases above 10 without too much strain (all the way up to 36). Also added here is the handling of negative numbers:
extension Int {
    func toBase(b:Int) -> String {
        guard b > 1 && b < 37 else {
            fatalError("base out of range")
        }
        let dict = [10:"A", 11:"B", 12:"C", 13:"D",14:"E",15:"F", 16:"G", 17:"H", 18:"I", 19:"J", 20:"K", 21:"L",22:"M", 23:"N", 24:"O", 25:"P", 26:"Q",27:"R", 28:"S", 29:"T", 30:"U", 31:"V", 32:"W", 33:"X", 34:"Y", 35:"Z"]
        var num = abs(self)
        var str = ""
        repeat {
            if let l = dict[num%b] {
            str = l + str
            }
            else {
            str = String(num%b) + str
            }
            
            num = num/b
        }
            while num > 0
        return self < 0 ? "-\(str)" : str

    }
}

172508.toBase(36) == String(172508, radix:36).uppercaseString // true

Note: You can learn about the theory on Purple Math or no doubt many other places.

Additional fun

It is possible to handle Double to binary as well with the help of the toBase() method:
extension Double {
    
    func toBinary() -> String {
        func afterDecimal(d:Double) -> String {
            var str = ""
            var num = d
            while num != 1 {
                num *= 2
                str = str + String(Int(num))
                if num > 1 {
                    num = num - Double(Int(num))
                }
            }
            return str
            
        }
    // divide into before and after decimal point
        let split = String(self).componentsSeparatedByString(".")
        let result = Int(split[0])!.toBase(2) + "." + afterDecimal(Double("0." + split[1])!)
        return result
    }
    
    
}


let abc = 2.122228867
    abc.toBinary()
This can be backwards checked using floating-point binary to Double conversion. But once again we can go further than binary and transform a Double into a String representation of any base between 2 and 36:
extension Double {

    // check results against http://baseconvert.com
    func toBase(b:Int, maxPrecision:Int = 100) -> String {
        func afterDecimal(d:Double) -> String {
            var str = ""
            var num = d
           
            let dict = [10:"A", 11:"B", 12:"C", 13:"D",14:"E",15:"F", 16:"G", 17:"H", 18:"I", 19:"J", 20:"K", 21:"L",22:"M", 23:"N", 24:"O", 25:"P", 26:"Q",27:"R", 28:"S", 29:"T", 30:"U", 31:"V", 32:"W", 33:"X", 34:"Y", 35:"Z"]
            for _ in 0..<100 {
                num *= Double(b)
                // if finished before maxPrecision return early
                if num % Double(b) == 0 {
                    return str
                }
                if let l = dict[Int(num)] {
                    str = str + l
                }
                else {
                    str = str + String(Int(num))
                }
                
                if num > 1 {
                    num = num - Double(Int(num))
                }
                
            }
            return str.stringByTrimmingCharactersInSet(NSCharacterSet(charactersInString: "0"))
            
        }
        // divide into before and after decimal point
        let split = String(self).componentsSeparatedByString(".")
        let result = Int(split[0])!.toBase(b) + "." + afterDecimal(Double("0." + split[1])!)
        return result
    }
}

let abc = 7.96
abc.toBase(16) // 7.F5C28F5C28F5C
Finally, the next step is to transform this String back to a Double and check that everything is working as it should.
extension String {
    func fromBase(b:Int, maxPrecision:Int = 100) -> Double {
        let dict:[Int:Character] = [10:"A", 11:"B", 12:"C", 13:"D",14:"E",15:"F", 16:"G", 17:"H", 18:"I", 19:"J", 20:"K", 21:"L",22:"M", 23:"N", 24:"O", 25:"P", 26:"Q",27:"R", 28:"S", 29:"T", 30:"U", 31:"V", 32:"W", 33:"X", 34:"Y", 35:"Z"]
        
        func beforeDecimal(str:String) -> Double {
            var num = 0.0
            for c in str.characters.reverse().enumerate() {
                let multi = pow(Double(b), Double(c.index))
                
                
                if let v = Int(String(c.element)) {
                    num += multi * Double(v)
                }
                else {
                    for (i, s) in dict where s == c.element {
                        num += multi * Double(i)
                    }
                }
                
            }
            return num
        }
        func afterDecimal(str:String) -> Double {
            var num = 0.0
            
            for c in str.characters.enumerate() {
                let multi = pow(1/Double(b), Double(c.index + 1))
                if let v = Int(String(c.element)) {
                    num += multi * Double(v)
                }
                else {
                    for (i, s) in dict where s == c.element {
                        num += multi * Double(i)
                    }
                }
                
            }
            
            return num
        }
        let split = self.componentsSeparatedByString(".")
        let isNegative = split[0].hasPrefix("-")
        if isNegative {
            split[0].removeAtIndex(split[0].startIndex)
        }
        let result = beforeDecimal(split[0]) + afterDecimal(split[1])
        return isNegative ? -result : result
    }
}

221.16.toBase(16).fromBase(16) // 221.16

let a = -136.36
a.toBase(36).fromBase(36) // -136.36

"C.3".fromBase(16) == 0xC.3p0 // true - see http://stackoverflow.com/a/29735236/1694526
So there we have it, a journey for floating-point numbers into another number base and back for bases 2 to 36.

Swift 3 (Xcode 8, beta 6)

func pow(base:Double, power:Int) -> Double {
    precondition(power >= 0)
    var answer : Double = 1
    for _ in 0..<power { answer *= base }
    return answer
}


extension String {
    func split(separator:String) -> [String] {
        return self.characters.split(separator: Character(separator)).map{String($0)}
    }
}


extension Int {
    func toBase(b:Int) -> String {
        guard b > 1 && b < 37 else {
            fatalError("base out of range")
        }
        let dict = [10:"A", 11:"B", 12:"C", 13:"D",14:"E",15:"F", 16:"G", 17:"H", 18:"I", 19:"J", 20:"K", 21:"L",22:"M", 23:"N", 24:"O", 25:"P", 26:"Q",27:"R", 28:"S", 29:"T", 30:"U", 31:"V", 32:"W", 33:"X", 34:"Y", 35:"Z"]
        var num = abs(self)
        var str = ""
        repeat {
            if let l = dict[num%b] {
                str = l + str
            }
            else {
                str = String(num%b) + str
            }
            
            num = num/b
        }
            while num > 0
        return self < 0 ? "-\(str)" : str
        
    }
}

172508.toBase(b: 36)

String(172508, radix:36).uppercased() // true
extension Double {
    
    func toBinary() -> String {
        func afterDecimal(d:Double) -> String {
            var str = ""
            var num = d
            while num != 1 {
                num *= 2
                str = str + String(Int(num))
                if num > 1 {
                    num = num - Double(Int(num))
                }
            }
            return str
            
        }
        // divide into before and after decimal point
        let split = String(self).split(separator: ".")
        let result = Int(split[0])!.toBase(b: 2) + "." + afterDecimal(d: Double("0." + split[1])!)
        return result
    }
    
    
}


let abc = 2.122228867
abc.toBinary()

extension Double {
    
    // check results against http://baseconvert.com
    func toBase(b:Int, maxPrecision:Int = 100) -> String {
        func afterDecimal(d:Double) -> String {
            var str = ""
            var num = d
            
            let dict = [10:"A", 11:"B", 12:"C", 13:"D",14:"E",15:"F", 16:"G", 17:"H", 18:"I", 19:"J", 20:"K", 21:"L",22:"M", 23:"N", 24:"O", 25:"P", 26:"Q",27:"R", 28:"S", 29:"T", 30:"U", 31:"V", 32:"W", 33:"X", 34:"Y", 35:"Z"]
            for _ in 0..<100 {
                num *= Double(b)
                // if finished before maxPrecision return early
                if num.truncatingRemainder(dividingBy: Double(b)) == 0 {
                    return str
                }
                if let l = dict[Int(num)] {
                    str = str + l
                }
                else {
                    str = str + String(Int(num))
                }
                
                if num > 1 {
                    num = num - Double(Int(num))
                }
                
            }
            // FIXME: trim zeroes
            return str
            
        }
        // divide into before and after decimal point
        let split = String(self).split(separator: ".")
        let result = Int(split[0])!.toBase(b:b) + "." + afterDecimal(d: Double("0." + split[1])!)
        return result
    }
}

let abcd = 7.96
abcd.toBase(b: 16) // 7.F5C28F5C28F5C
extension String {
    func fromBase(b:Int, maxPrecision:Int = 100) -> Double {
        let dict:[Int:Character] = [10:"A", 11:"B", 12:"C", 13:"D",14:"E",15:"F", 16:"G", 17:"H", 18:"I", 19:"J", 20:"K", 21:"L",22:"M", 23:"N", 24:"O", 25:"P", 26:"Q",27:"R", 28:"S", 29:"T", 30:"U", 31:"V", 32:"W", 33:"X", 34:"Y", 35:"Z"]
        
        func beforeDecimal(str:String) -> Double {
            var num = 0.0
            for c in str.characters.reversed().enumerated() {
                let multi = pow(base: Double(b), power: c.offset)
                
                
                if let v = Int(String(c.element)) {
                    num += Double(multi) * Double(v)
                }
                else {
                    for (i, s) in dict where s == c.element {
                        num += Double(multi) * Double(i)
                    }
                }
                
            }
            return num
        }
        func afterDecimal(str:String) -> Double {
            var num = 0.0
            
            for c in str.characters.enumerated() {
                let multi = pow(base: 1/Double(b), power: c.offset + 1)
                if let v = Int(String(c.element)) {
                    num += Double(multi) * Double(v)
                }
                else {
                    for (i, s) in dict where s == c.element {
                        num += Double(multi) * Double(i)
                    }
                }
                
            }
            
            return num
        }
    
        var split = self.split(separator:  ".")
        let isNegative = split[0].hasPrefix("-")
        if isNegative {
            split[0].remove(at: split[0].startIndex)
        }
        let result = beforeDecimal(str: split[0]) + afterDecimal(str: split[1])
        return isNegative ? -result : result
    }
}

221.16.toBase(b: 16).fromBase(b: 16) // 221.16

let a = -136.36
a.toBase(b: 36).fromBase(b: 36) // -136.36

"C.3".fromBase(b: 16) == 0xC.3p0 // true - see http://stackoverflow.com/a/29735236/1694526




Comments