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?
Note: You can learn about the theory on Purple Math or no doubt many other places.
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) // trueWe 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.F5C28F5C28F5CFinally, 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/1694526So 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
Post a Comment