swift中的进制转换,以及玩转二进制
swift中的进制转换,以及玩转二进制
在日常开发中我们很少用到进制转换,或操作二进制的情况。但是如果你处理一些底层的代码,你会经常与二进制打交道,所以接下来我们先来了解一下二进制。
二进制(binary),是在数学和数字电路中以2为基数的记数系统,是以2为基数代表系统的二进位制。这一系统中,通常用两个不同的符号0(代表零)和1(代表一)来表示。数字电子电路中,逻辑门的实现直接应用了二进制,现代的计算机和依赖计算机的设备里都使用二进制。每个数字称为一个比特(Bit,Binary digit的缩写)【摘自百度百科】
因此在计算机的世界里只有0和1。
在swift中为我们提供了存储不同长度bit的基本类型,例如UInt8(无符号的8bit,我们常说是无符号的8位Int型),Int8(有符号的8bit)等。符号位一般是二进制的最高位表示,0:正数,1:负数。
我们以十进制+8和-8来举例8位二进制中的符号位(这里操作小端模式举例,关于大小端模式的介绍,可以看这里):
+8的二进制:00001000,最高位这里是0,因此代表正数
-8的二进制:10001000,最高位这里是1,因此代表负数
进制的表示法
二进制:b(Binary),swift中可以这样表示:let a = 0b0000_1001
八进制:o(Octal),swift中可以这样表示:let a = 0o11
十进制:d(Decimal),swift中可以这样表示:let a = 9
十六进制:x(Hexadecimal),swift中可以这样表示:let a = 0x9
接下来就来讲讲进制之间的转换原理
二进制——>十进制
00001001——>9
转换原理:(因为是小端模式,二进制的有效位是从右到左的)1*2^0 + 0*2^1 + 0*2^2 + 1*2^3 + 0*2^4+ 0*2^5+ 0*2^6+ 0*2^7 = 9
swift代码实现:
func decimal(_ v: String) -> Int { guard v.count > 0 else { return 0 } let l = v.count var isSkip = true var sum: Int = 0 var idx = 0, skipCount = 0 for i in v.indices { if let a = Int(String(v[i])) { if isSkip && a > 0{ isSkip = false } if isSkip { skipCount += 1 continue } sum += a*Int(pow(2, Double(l-skipCount-1-idx))) idx += 1 } } return sum }
swift自带的api
let b = 0b0000_1001 let s = String(b, radix: 10) // 转成10进制字符 print("---: \(s)") // 输出结果:---: 9
十进制——>二进制
9——>00001001
转换原理:(根据二进制 转 十进制可以推导出,注意这里是小端模式,因此每次计算出来的bit顺序应该是从右到左,因为我们只要8位长度的二进制,因此这里需要计算8次)
func toBinary(_ v: UInt8) -> String { var str: String = "" var i = v while str.count < v.bitWidth { str.insert(Character("\(i % 2)"), at: str.startIndex) i /= 2 } return str }
swift自带api
let a: UInt8 = 9 let str = String(a, radix: 2) print("str: \(str)") // 输出:str: 1001 // 它输出的字符串只包括有效二进制位
二进制——>八进制
00001001——>11
转换原理:(因为是小端模式,二进制的有效位是从右到左的)二进制每三位一组(不够三位补0),分别计算对应的十进制值,计算结果的组合就是一个八进制数。
1、分组:000(不足三位补0),001,001
2、每组分别计算十进制值:0*2^0 + 0*2^1 + 0*2^2 = 0,1*2^0 + 0*2^1 + 0*2^2 = 1,1*2^0 + 0*2^1 + 0*2^2 = 1
3、每组的结果从左到右组合:0,1,1,最终的八进制数字为:11
swift代码实现:
func octal(_ v: String) -> String { guard v.count > 0 else { return "" } var res = "" var idx = 0 var sum = 0 for i in v.indices.reversed() { if let a = Int(String(v[i])) { sum += a*Int(pow(2, Double(idx))) if idx >= 2 { res.insert(Character("\(sum)"), at: res.startIndex) idx = 0 sum = 0 }else { idx += 1 } } } return res } print("----o: \(octal("00001011"))") // 输出:----o: 13
swift自带api:
let a = 0b0000_1011 let str = String(a, radix: 8) print("str: \(str)") // 输出:str: 13
八进制——>二进制
11——>00001001
原理:(利用二进制 转 八进制 反推导可知,对八进制的每位分别进行转3位的二进制数,然后将每位的结果拼接)1->001,1->001,最后拼接得到二进制:001001
swift实现代码:
func octalToBinary(_ v: String) -> String { guard v.count > 0 else { return "" } var res = "" for i in v.indices { if let a = Int(String(v[i])) { var n = a var s = "" for _ in 0..<3 { s.insert(Character("\(n % 2)"), at: s.startIndex) n /= 2 } res.append(s) } } return res } print("----b: \(octalToBinary("11"))") // 八进制:11转二进制 // 输出:----b: 001001
swift自带api
let a = 0o11 let str = String(a, radix: 2) print("str: \(str)") // 输出:str: 1001
二进制——>十六进制
00101011——>2b
原理:二进制每四位一组(不够四位补0),分别计算对应的十进制值,计算结果的组合就是一个八进制数
1、分组:0010,1001
2、分别计算每组的十进制值:0*2^0 + 1*2^1 + 0*2^2 + 0*2^3 = 2,1*2^0 + 0*2^1 + 0*2^2 + 1*2^3 = 11(因为在16进制的表示中是从0到f的,因此11对应的是b)
3、组合结果:2b
swift代码实现:
func hex(_ v: String) -> String { guard v.count > 0 else { return "" } var res = "" var idx = 0, sum = 0 for i in v.indices.reversed() { if let a = Int(String(v[i])) { sum += (a*Int(pow(2, Double(idx)))) if idx >= 3 { res.insert(Character(String(format: "%x", sum)), at: res.startIndex) idx = 0 sum = 0 }else { idx += 1 } } } if sum > 0 { res.insert(Character("\(sum)"), at: res.startIndex) } return res } print("----hex: \(hex("101011"))") // 二进制:101011转十六进制 // 输出:----hex: 2b
swift自带api:
let a = 0b0010_1011 let str = String(a, radix: 16) print("str: \(str)") // 输出:str: 2b
十六进制——>二进制
2b——>00101011
原理:(根据二进制 转 十六进制 可推导出,对十六进制的每位分别进行转4位的二进制数,然后将每位的结果拼接)2——>0010,b——>1011
swift实现代码:
func hexToBinary(_ v: String) -> String { guard v.count > 0 else { return "" } var res = "" let map = ["1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "a": 10, "b": 11, "c": 12, "d": 13, "e": 14, "f": 15] for i in v.indices { if let a = map[String(v[i])] { var n = a var s = "" for _ in 0..<4 { s.insert(Character("\(n % 2)"), at: s.startIndex) n /= 2 } res.append(s) } } return res } print("----b: \(hexToBinary("2b"))") // 十六进制:2b转二进制 // 输出:----b: 00101011
swift自带api
let a = 0x2b let str = String(a, radix: 2) print("str: \(str)") // 输出:str: 101011
八进制——>十进制
0o1101——>577
原理:(类似于二进制 转 十进制,因为是小端模式,分别从右往左对八进制每位进行加权计算,然后将所有加权的值累加即为 十进制结果)
1*8^0 + 0*8^1 + 1*8^2 + 1*8^3 = 557
swift实现代码:
func octalToDecimal(_ v: String) -> Int { guard v.count > 0 else { return 0 } var idx = 0, sum = 0 for i in v.indices.reversed() { if let a = Int(String(v[i])) { sum += (a*Int(pow(8, Double(idx)))) idx += 1 } } return sum } print("----d: \(octalToDecimal("1101"))") // 八进制:1101转十进制 // 输出:----d: 577
swift自带api:
let a = 0o1101 let str = String(a, radix: 10) print("str: \(str)") // 输出:str: 577
十进制——>八进制
577——>1101
原理:(根据八进制 转 十进制 可推导出,对十进制数 除以 8,余数作为二进制有效位,商>0,继续对商进行上面的操作,直到商==0为止)
swift实现代码:
func decimalToOctal(_ v: Int) -> String { guard v > 0 else { return "0" } var str = "" var a = v while a > 0 { str.insert(Character("\(a % 8)"), at: str.startIndex) a /= 8 } return str } print("---o: \(decimalToOctal(577))") // 十进制:577 转 八进制 // 输出:---o: 1101
swift自带api:
let a = 577 let str = String(a, radix: 8) print("str: \(str)") // 输出:str: 1101
八进制——>十六进制
1271——>2b9
原理:(中转法)现将八进制 转成 二进制 或 十进制,然后通过二进制 或 十进制 再转 十六进制。
swift自带api:
let a = 0o1271 let str = String(a, radix: 16) print("str: \(str)") // 输出:str: 2b9
十六进制——>八进制
2b9——>1271
原理:(中转法)现将十六进制 转成 二进制 或 十进制,然后通过二进制 或 十进制 再转 八进制。
swift自带api:
let a = 0x2b9 let str = String(a, radix: 8) print("str: \(str)") // 输出:str: 1271
以上就是全部的进制之间的转化过程,接下来介绍一下对二进制操作常用到的位运算符
“&”运算符
两个二进制对应位都为1,该位的结果才为1,否则为0。如下:
第一个二进制 | 0 | 1 | 0 | 1 | 1 | 0 | 0 | 1 |
第二个二进制 | 0 | 0 | 1 | 1 | 1 | 0 | 1 | 0 |
&运算后的结果 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 |
&运算符在日常开发中用处比较多,举个最长用到的例子,在OC开发中,我们定义枚举,经常会利用&来判断一个包含多个枚举值的情况:
NS_ENUM(NSInteger, MyType) { MyType1 = 1 << 0, MyType2 = 1 << 1, MyType3 = 1 << 2, }; enum MyType type = MyType1 | MyType2; if ((type & MyType1) == MyType1) { NSLog(@"-----type中包含MyType1枚举值"); }else { NSLog(@"-----type中不包含MyType1枚举值"); } if ((type & MyType2) == MyType2) { NSLog(@"-----type中包含MyType2枚举值"); }else { NSLog(@"-----type中不包含MyType2枚举值"); } if ((type & MyType3) == MyType3) { NSLog(@"-----type中包含MyType3枚举值"); }else { NSLog(@"-----type中不包含MyType3枚举值"); } // 输出: // -----type中包含MyType1枚举值 // -----type中包含MyType2枚举值 // -----type中不包含MyType3枚举值
我们也可以利用&运算符来判断一个数是奇数还是偶数,只要二进制位第0位=1,那这个数必定是奇数,如果=0,那就是偶数。为什么?根据上面介绍的二进制转十进制可知:第一位的值要么是0(0*2^0),要么是1(1*2^0),而后面位数对应的值都是2的倍数(偶数),因此一个偶数+1=奇数。这样我们只需要判断第一位是1还是0,就可以判断出这个数是奇还是偶 。如何判断二进制第一位是1还是0呢,我们可以让这个数与1进行&运算,即x & 0000 0001 = 1(奇数),x & 0000 0001 = 0(偶数),如下:
let x = 123 if x & 1 == 0 { print("偶数") }else { print("奇数") }
“|”运算符
两个二进制对应位只要有一个为1,该位的结果就为1,否则为0。如下:
第一个二进制 | 0 | 1 | 0 | 1 | 1 | 0 | 0 | 1 |
第二个二进制 | 0 | 0 | 1 | 1 | 1 | 0 | 1 | 0 |
|运算后的结果 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 1 |
|运算符常用于将多个枚举值合并,例如上面的例子中,将两个枚举值合并到一个type变量中。
“^”(异或)运算符
两个二进制对应位不相同结果则为1,相同则为0。如下:
第一个二进制 | 0 | 1 | 0 | 1 | 1 | 0 | 0 | 1 |
第二个二进制 | 0 | 0 | 1 | 1 | 1 | 0 | 1 | 0 |
^运算后的结果 | 0 | 1 | 1 | 0 | 0 | 0 | 1 | 1 |
^运算符也有一些使用的场景,例如我们可以利用该运算符实现 不占用额外空间交换两个变量的值。如下:
var x = 1024, y = 4201 x = x^y y = x^y // x x = x^y // y print("x: \(x), y: \(y)") // 输出:x: 4201, y: 1024
我们用一个简单的两个二进制数解释上面的例子:
数字1: | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
数字2: | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
^结果 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
^结果与 数字1 再次进行^运算
数字1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
^结果 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
结果 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
它的结果正是数字2。同样的道理,让^结果与 数字2 再次进行 ^运算,结果将会是数字1。
另外^运算还有个特性,那就是任何一个数与自己^运算,相当于将自己至为0。
var x = 100123 x ^= x print("x: \(x)") // 输出:x: 0
“~”(取反)运算符
对一个二进制取反就是对二进制中的每位取反,例如0取反就是1,1取反就是0。如下:
一个二进制 | 0 | 1 | 0 | 1 | 1 | 0 | 0 | 1 |
~结果 | 1 | 0 | 1 | 0 | 0 | 1 | 1 | 0 |
我们可以利用取反运算符和&运算结合,从而实现从多个合并的枚举中删除指定的枚举。如下:
NS_ENUM(NSInteger, MyType) { MyType1 = 1 << 0, MyType2 = 1 << 1, MyType3 = 1 << 2, }; enum MyType type = MyType1 | MyType2; if ((type & MyType1) == MyType1) { NSLog(@"-----type中包含MyType1枚举值"); // 删除枚举值 MyType1 type &= ~MyType1; if ((type & MyType1) == 0) { NSLog(@"枚举MyType1从type变量中移除"); } }
我们也可以通过该运算符计算一个数的相反数,如下:(一个数x的相反数=~x + 1)
let x = -123 print("x: \(~x + 1)") // 计算x的相反数 // 输出:x: 123
“<<”左移运算符
对二进制的每位进行左移,右侧空出的位用0补,运算符后面跟着移动的位数,例如:x << 2,对x进行左移2位。
一个二进制 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 |
<<4结果 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
由于二进制每位之间都相差2^n倍数,因此对一个数进行左移运算,相当于这个数乘以2^n(n:移动的位数)
例如:3 << 1:相当于:3 * 2^1 = 6。因此要求一个数的2的幂数,可以这样计算:1 << n:相当于:1 * 2^n
“>>”右移运算符
与左移运算符相反,它是对对二进制的每位进行右移,左侧空出的位用0补,运算符后面跟着移动的位数,例如:x >> 2,对x进行右移2位。
右移运算相当于这个数除以2^n(n:移动的位数)
一个二进制 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
>>4结果 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 |
左移与右移运算也在开发中常用到,例如我们可以通过对一个数右移一位来实现除以2的操作。
另外还有用于取指定位的值,例如:要取一个八位无符号的二进制的前4位的数值,如下:
1、二进制:1111 0000
2、对二进制右移4位:0000 1111
3、计算十进制数值:1*2^0 + 1*2^1 + 1*2^2 + 1*2^3 = 15
在取一个图片中指定像素点的色值的时候,我们就会用到移位算法。一个色值是包括r、g、b、a四个分量,根据不同的颜色通道的mode,它们的排列顺序也不同。一般以r、g、b、a顺序居多。颜色的每个分量采用8位二进制数表示,因此一个色值的位数可以是32位。根据它们的排列顺序,我们就可以通过移位运算,来分别取出每个分量值。例如:
----------------x----------------
---r---- ---g--- ---b--- ---a---
00000100 00001001 00000010 00000001
r:x >> 24
g:(x << 8) >> 24
b:(x << 16) >> 24
a:(x << 24) >> 24
其实对于上面的取值还有一些技巧,比如在取b分量的值时,我们可以通过x&00000000_00000000_11111111_00000000来快速取值,这样就不需要移位了。一般我们会采用十六进制来表示这样的一串二进制数。我们知道十六进制中最大值是F,而每个十六进制数占4位,因此8位的最大值就是八个1,用十六进制表示就是:FF。
下面将展示不通过移位方法来去各个分量的值。
r:x & 0xFF000000
g:x & 0x00FF0000
b:x & 0x0000FF00
a:x & 0x000000FF
这种计算方法会减少很多移位的步骤,因此这样计算的效率肯定比移位方法要高。
“>>>”(无符号右移)运算符(某些开发语言中不支持该运算符,例如:swift、OC)
这个运算符跟>>运算符类似,都是将二进制位右移,而>>>在移动时不会考虑符号位,会将符号位用0补。而>>在移动的时候会考虑符号位,并不会用0来补充符号位。因此通过>>>运算后得到的值肯定是一个正数。
额外知识
1、关于如何完整打印出二进制位的字符串,下面是问题描述:
Int8:代表8位二进制位的整型,其二进制位字符串完整输出应该是长度为8的字符串,例如:00001111。
知识点,关于Int(Int8、Int16、Int32、Int64)的属性介绍:
let a = Int8(12) // 拿Int8为例 print("非完整的二进制字符串:\(String(a, radix: 2))") // 二进制字符串:1100 print("bit位数:\(a.bitWidth), ") // 8 print("bit位左侧(头部)0的个数:\(a.leadingZeroBitCount)") // 4 print("bit位右侧(尾部)0的个数:\(a.trailingZeroBitCount)") // 2 print("非0比特位的个数:\(a.nonzeroBitCount)") // 2
综上知识点,我们可以打印完整的二进制位字符串的方法如下:
let a = Int8(12) // 拿Int8为例 let bitStr = String(repeating: "0", count: a.leadingZeroBitCount) + String(a, radix: 2) print("完整的二进制字符串:\(bitStr)") // 00001100
2、如果获取下一个2的幂数,问题描述如下:
2的幂数列表为:2^0、2^1、2^2、2^3、2^4...,即:1 、2 、4 、8 、16...
给出一个数,返回这个数的下一个2的幂数,例如:1->1、2->2、3->4、4->4、5->8、6->8、7->8、8->8、9->16、10->6、11->16....
// 例如:给出一个数n let n = 6 let nextPowerOf2 = 1 << (n.bitWidth - (n - 1).leadingZeroBitCount) print("nextPowerOf2: \(nextPowerOf2)") // 8