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:移动的位数)

“>>”右移运算符

与左移运算符相反,它是对对二进制的每位进行右移,左侧空出的位用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

左移与右移运算也在开发中常用到,一般用于取指定位的值,例如:要取一个八位无符号的二进制的前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. Java中的Exception
  2. C# 获取本机指定类型指定网卡的Ip地址
  3. Android ActionBar Home按钮返回事件处理的两种方式
  4. sensor_HAL分析
  5. 部署WEB应用程序
  6. L2-002. 链表去重
  7. php 创建和修改文件内容
  8. PC端问题列表及解决方案
  9. 【转】Reflector、reflexil、De4Dot、IL相关操作指令合集
  10. 亚马逊VE账号运营
  11. Linux期末复习题
  12. java基础-网络编程(Socket)技术选型入门之NIO技术
  13. Caffe SSD AttributeError: &#39;module&#39; object has no attribute &#39;LabelMap&#39;
  14. pionter指针小结
  15. PowerDesigner如何将消失的工具栏显示出来
  16. canvas小球 时间倒计时demo-优化
  17. array_unique() 去重复
  18. opencv中矩阵计算的一些函数
  19. 把jpg文件读取到内存char* 再转换成CImage
  20. jQuery+css模拟下拉框模糊搜索的实现

热门文章

  1. 工具-使用org.openjdk.jol查看对象在内存中的布局
  2. 另类数据获取法-eax法
  3. 【KAWAKO】MNN-将推理程序交叉编译成RK1126的可执行文件
  4. 物语(monogatari)
  5. OpenLayers多源数据加载
  6. day09-MyBatis缓存
  7. [C#]问号?和双问号??
  8. ModuleNotFoundError:No module named &#39;past&#39; 问题及解决方法
  9. spring RedisTemplate用法
  10. CLIP 读书笔记