/*Swift有许多可以对数值进行操作的高级运算符,比如位运算和一位运算。
与C语言中的算术运算符不同,Swift 中的算术运算符默认是不会溢出的。所有溢出行为都会被捕获并报告为错误。
如果想让系统允许溢出行为,可以选择使用 Swift 中另一套默认支持溢出的运算符,比如溢出加法运算符(&+)。
所有的这些溢出运算符都是以 & 开头的。
在定义自有的结构体、类和枚举时,最好也同时为它们提供标准Swift运算符的实现。Swift简化了运算符的自定义实现,
也使判断不同类型所对应的行为更为简单。
我们不用被预定义的运算符所限制。在 Swift 当中可以自由地定义中缀、前缀、后缀和赋值运算符,以及相应的优先级与结合性。
这些运算符在代码中可以像预设的运算符一样使用,我们甚至可以扩展已有的类型以支持自定义的运算符。
*/
/*按位取反运算符(bitwise NOT operator)
按位取反运算符(~) 可以对一个数值的全部位进行取反:
按位取反操作符是一个前置运算符,需要直接放在操作数的之前,并且它们之间不能添加任何空格。
总结:按位把0变成1,把1变成0
*/
let initialBits: UInt8 = 0b11111100 //0b 二进制表示
let invertedBits: UInt8 = ~initialBits //等于0b00000011
print(invertedBits) // 3
/* 按位与运算符(&)
按比特位对两个数合并,位都为1,合并为1,否者为0.
*/
let firstSixBits: UInt8 = 0b11111100
let lastSixBits: UInt8 = 0b00111111
let middleFourBits = firstSixBits & lastSixBits // 等于0b00111100
print(middleFourBits)//60
/*按位或运算符(|)
按位或运算符(|)可以对两个数的比特位进行比较。它返回一个新的数,只要两个操作数的对应位中有任意一个为 1 时,该数的对应位就为 1。
*/
let someBits: UInt8 = 0b10110010
let moreBits: UInt8 = 0b01011110
let combinedbits = someBits | moreBits //等于0b11111110
print(combinedbits)//254
/*按位异或运算符
按位异或运算符(^)可以对两个数的比特位进行比较。它返回一个新的数,当两个操作数的对应位有一个为1,但不同时为1,
该数的对应位就为 1:
*/
let firstBits: UInt8 = 0b00010100
let otherBits: UInt8 = 0b00000101
let outputBits = firstBits ^ otherBits //等于0b00010001
print(outputBits)//17
/*按位左移/右移运算符
按位左移运算符(<<)和按位右移运算符(>>)可以对一个数进行指定位数的左移和右移,但是需要遵守下面定义的规则。
对一个数进行按位左移或按位右移,相当于对这个数进行乘以 2 或除以 2 的运算。将一个整数左移一位,等价于将这个数乘以 2,
同样地,将一个整数右移一位,等价于将这个数除以 2。
无符号整型的移位操作
对无符号整型进行移位的规则如下:
已经存在的比特位按指定的位数进行左移和右移。
任何移动超出整型存储边界的位都会被丢弃。
用 0 来填充移动后产生的空白位。
这种方法称为逻辑移位(logical shift)。
*/
let shiftBits: UInt8 = 4 //即二进制到00000100
shiftBits << 1 //00001000
print(shiftBits << 1)//8
shiftBits << 2 //00010000
print(shiftBits << 2)//16
shiftBits << 5 //10000000
print(shiftBits << 5)//128
shiftBits << 6 //00000000
print(shiftBits << 6)//0
shiftBits >> 2 //00000001
print(shiftBits >> 2)//1
//可以使用移位操作对其他的数据类型进行编码和解码:
//2进制转16进制,就是以四位为一段,分别转换为16进制
let pink: UInt32 = 0xcc6699 //UInt32 存储32位
let redComponent = (pink & 0xFF0000) >> 16 //等于0x0000cc == 0xcc
print(redComponent) //204
let greenComponent = (pink & 0x00FF00) >> 8 //等于0x0066 == 0x66
print(greenComponent)//102
let blueComponent = pink & 0x0000FF //等于0x000099 == 0x99
print(blueComponent)//153
/*有符号整数的移位操作
//对比无符号整型来说,有符整型的移位操作相对复杂得多,这种复杂性源于有符号整数的二进制表现形式。
有符号整数使用第 1 个比特位(通常被称为符号位)来表示这个数的正负。符号位为 0 代表正数,为 1 代表负数。
(故有符号整数的表示范围为-2的(n-1)次方 到 2的(n-1)次方.例如:UInt8: -2^(8-1)~~2^(8-1))
其余的比特位(通常被称为数值位)存储了这个数的真实值。有符号正整数和无符号数的存储方式是一样的,都是从 0 开始算起。
计算机中的符号数有三种表示方法,即原码、反码和补码。三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位,三种表示方法各不相同。
在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理。此外,补码与原码相互转换,其运算过程是相同的,
不需要额外的硬件电路。
补码的特质:
1、一个负整数(或原码)与其补数(或补码)相加,和为模(一个计量系统的计数范围)。
2、对一个整数的补码再求补码,等于该整数自身。
3、补码的正零与负零表示方法相同。
注:为了便于正确计算负数采用补码的方式表示。(补码=反码+1)
正数的反码和补码等于原码(原码即原二进制表示形式)
负数的反码等于在原码除符号位外,其他位取反
负数的补码等于反码在最后一位加1
负数的原码等于把对应正数最高位变成1
负数的表示通常被称为二进制补码(two's complement)表示法。用这种方法来表示负数乍看起来有点奇怪,但它有几个优点。
1.首先,如果想对 -1 和 -4 进行加法操作,我们只需要将这两个数的全部 8 个比特位进行相加,并且将计算结果中超出 8 位的数值丢弃:
1 1 1 1 1 1 0 0 //-4的补码
+ 1 1 1 1 1 1 1 1 //-1的补码
____________________________
= 1 1 1 1 1 1 0 1 1 (得到补码)1111 1011 然后求反码得到: 1000 0100,再求补码得到:1000 0101 = -5
2.使用二进制补码可以使负数的按位左移和右移操作得到跟正数同样的效果,即每向左移一位就将自身的数值乘以 2,
每向右一位就将自身的数值除以 2。要达到此目的,对有符号整数的右移有一个额外的规则:
当对正整数进行按位右移操作时,遵循与无符号整数相同的规则,但是对于移位产生的空白位使用符号位进行填充,而不是用 0。
*/
/*溢出运算符
在默认情况下,当向一个整数赋超过它容量的值时,Swift 默认会报错,而不是生成一个无效的数。
这个行为给我们操作过大或着过小的数的时候提供了额外的安全性。
也可以选择让系统在数值溢出的时候采取截断操作,而非报错。可以使用 Swift 提供的三个溢出操作符(overflow operators)
来让系统支持整数溢出运算。这些操作符都是以 & 开头的:
溢出加法 &+
溢出减法 &-
溢出乘法 &*
对于无符号与有符号整型数值来说,当出现上溢时,它们会从数值所能容纳的最大数变成最小的数。同样地,当发生下溢时,它们会从所能容纳的最小数变成最大的数。
*/
//数值溢出:数值有可能出现上溢或者下溢
var unsignedOverflow = UInt8.max
unsignedOverflow = unsignedOverflow &+ 1
print(unsignedOverflow) //0
/*上面例子为上溢
二进制运算过程表示如下:
1 1 1 1 1 1 1 1 //255
&+ 0 0 0 0 0 0 0 1 //1
————————————————————————
=1 0 0 0 0 0 0 0 0
原理:各位相加,逢2进1位,最后1出现在最左边,超出8位,舍去这位,故计算结果为0
*/
var unsflow = UInt8.min
var f1 = unsflow &- 1
print(unsflow) //255
var signedOverflow = Int8.min
signedOverflow = signedOverflow &- 1
print(signedOverflow) //127