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来补充符号位。因此通过>>>运算后得到的值肯定是一个正数。

swift中的进制转换,以及玩转二进制的更多相关文章

  1. JS中的进制转换

    1 前言 js的进制转换, 分为2进制,8进制,10进制,16进制之间的相互转换, 我们直接利用 对象.toString()即可实现. 仅作为记录. 2 代码 //10进制转为16进制 (10).to ...

  2. java中16进制转换10进制

    java中16进制转换10进制 public static void main(String[] args) { String str = "04e1"; String myStr ...

  3. Oracle 中的进制转换

    Oracle 中的进制转换 */--> Oracle 中的进制转换 Table of Contents 1. 进制名 2. 10进制与16进制互相转换 2.1. 10进制转换为16进制 2.2. ...

  4. java中的进制转换

    java中的进制转换及转换函数 转自:https://blog.csdn.net/V0218/article/details/74945203 Java的进制转换 进制转换原理 十进制 转 二进制: ...

  5. JS中的进制转换以及作用

    js的进制转换, 分为2进制,8进制,10进制,16进制之间的相互转换, 我们直接利用 对象.toString()即可实现: //10进制转为16进制 ().toString() // =>&q ...

  6. iOS蓝牙中的进制转换

    Bluetooth4.0.jpg 最近在忙一个蓝牙项目,在处理蓝牙数据的时候,经常遇到进制之间的转换,蓝牙处理的是16进制(NSData),而我们习惯的计数方式是10进制,为了节省空间,蓝牙也会把16 ...

  7. iOS蓝牙中的进制转换,数据格式转换

    最近在忙一个蓝牙项目,在处理蓝牙数据的时候,经常遇到进制之间的转换,蓝牙处理的是16进制(NSData),而我们习惯的计数方式是10进制,为了节省空间,蓝牙也会把16进制(NSData)拆成2进制记录 ...

  8. python中的进制转换

    python中常用的进制转化通常有两种方法: 1.用内置函数hex(),oct(),bin(),对应的数字表示为0x,0o,0b,功能是把十进制数字转化为其他进制  >>> int( ...

  9. Delphi中的进制转换

    二进制转换 function binToDec(Value: string): integer; var str: string; i: integer; begin Str := UpperCase ...

  10. java中的进制转换以及字符串类和数值类的相互转化

    import java.util.*; import java.io.*; import java.math.*; import java.math.*; public class Main { pu ...

随机推荐

  1. Nginx 安装perl

    1 安装包下载 https://www.cpan.org/src获取最新偶数版本下载链接并替换(偶数版本为稳定版) 2 上传到服务器解压 tar -zxvf perl-5.36.0.tar.gz 3 ...

  2. 云萌 V2.6.3.0 win10,win11 Windows永久激活工具

    Windows如果一直不激活,其实用起来问题也不大,除了无法修改壁纸.颜色.锁屏.主题以及无法使用微软账号的同步功能等之外,绝大多数的基本功能都可以正常使用.不过该激活还是得激活的.别的不说,就桌面右 ...

  3. C#通过OLEDB将DataTable写入Excel文件中

    利用OLEDB将DataTable数据写入Excel文件中,如果数据量过多,执行效率很缓慢,大数据量不推荐使用此方法. /// <summary> /// 创建DataTable /// ...

  4. Nodejs 使用 ZooKeeper 做服务发现

    将单体服务拆分为微服务后,为了服务高可用,一般会做集群多实例.但在分布式下,怎么进行高效.便捷的进行服务访问问题,出现了各类服务注册和服务发现框架.这里使用的是Zookeeper.ZooKeeper ...

  5. js程序

    JavaScript 程序 计算机程序是由计算机"执行"的一系列"指令". 在编程语言中,这些编程指令被称为语句. JavaScript 程序就是一系列的编程语 ...

  6. 栈——stack的用法

    介绍 栈(stack)又名堆栈,它是一种运算受限的线性表.限定仅在表尾进行插入和删除操作的线性表.这一端被称为栈顶,相对地,把另一端称为栈底.向一个栈插入新元素又称作进栈.入栈或压栈,它是把新元素放到 ...

  7. 把Excel自动转换Json格式

    Excel表格转JSON      在实际工作中,我们常常使用Excel记录各种数据,但在各种应用系统传输数据却使用JSON格式,这就需要把Excel转为JSON.如果能把数据转换传输过程自动化就更完 ...

  8. 一份随笔让你了解这个基于Raspberry Pi / 树莓派而设计的工业计算机

    CM4 Sensing是一款基于Raspberry Pi / 树莓派 计算模块4(简称CM4),由 EDATEC 为物联网和数据采集应用而设计的工业计算机.它充分利用了CM4的结构灵活性,解决了CPU ...

  9. js循环判断创建新对象放数组中

    原效果 之后效果: <!doctype html> <html lang="en"> <head> <meta charset=" ...

  10. postgresql 常用的删除重复数据方法

    一. 最高效方法 测试环境验证,6600万行大表,删除2200万重复数据仅需3分钟 delete from deltest a where a.ctid = any(array (select cti ...