前置阅读:html
有符号二进制整数有正数和负数。在 x86 处理器中,MSB 表示的是符号位:0 表示正数,1 表示负数。下图展现了 8 位的正数和负数:编程
概念总结:segmentfault
表示正数的补码能够直接转成十进制,表示负数的补码想要转回十进制步骤以下:安全
反码能够经过二进制值按位取反获得(全部二进制位都取反)
正数的反码示例:并发
十进制数值 | 补码 | 反码 |
---|---|---|
0 | 0000 0000 | 1111 1111 |
1 | 0000 0001 | 1111 1110 |
2 | 0000 0010 | 1111 1101 |
3 | 0000 0011 | 1111 1100 |
4 | 0000 0100 | 1111 1011 |
负数的反码示例:函数
十进制数值 | 补码 | 反码 |
---|---|---|
-0 | 0000 0000 | 1111 1111 |
-1 | 1111 1111 | 0000 0000 |
-2 | 1111 1110 | 0000 0001 |
-3 | 1111 1101 | 0000 0010 |
在计算机内全部数值底层都用补码表示,不管正负数(十进制)
十进制数值 | 补码 |
---|---|
0 | 0000 0000 |
1 | 0000 0001 |
2 | 0000 0010 |
3 | 0000 0011 |
-0 | 0000 0000 |
-1 | 1111 1111 |
-2 | 1111 1110 |
-3 | 1111 1101 |
负数补码计算过程示例:ui
十进制数值 | 绝对值 | 绝对值补码 | 绝对值补码取反 | 绝对值补码取反加一 | 正确补码 | 十进制数值 |
---|---|---|---|---|---|---|
-0 | 0 | 0000 0000 | 1111 1111 | 1111 1111 + 1 ————— 1,0000 0000 |
0000 0000 | -0 |
-1 | 1 | 0000 0001 | 1111 1110 | 1111 1110 + 1 ————— 1111 1111 |
1111 1111 | -1 |
-2 | 2 | 0000 0010 | 1111 1101 | 1111 1101 + 1 ————— 1111 1110 |
1111 1110 | -2 |
-3 | 3 | 0000 0011 | 1111 1100 | 1111 1100 + 1 ————— 1111 1101 |
1111 1101 | -3 |
-4 | 4 | 0000 0100 | 1111 1011 | 1111 1011 + 1 ————— 1111 1100 |
1111 1100 | -4 |
-5 | 5 | 0000 0101 | 1111 1010 | 1111 1010 + 1 ————— 1111 1011 |
1111 1011 | -5 |
表示正数的补码能够直接转成十进制,表示负数的补码想要转回十进制步骤以下:atom
- 对表示负数的补码取反码加一获得负数的十进制绝对值补码;
- 再将负数的十进制绝对值补码转成十进制获得负数的十进制绝对值;
- 最后加上符号位;
MSB | 补码 | 十进制数值 |
---|---|---|
0 | 0000 0000 | 0 |
0 | 0000 0001 | 1 |
0 | 0000 0010 | 2 |
0 | 0000 0011 | 3 |
0 | 0000 0100 | 4 |
0 | 0000 0101 | 5 |
1 | 1111 1111 | -1 |
1 | 1111 1110 | -2 |
1 | 1111 1101 | -3 |
1 | 1111 1100 | -4 |
1 | 1111 1011 | -5 |
负数转换示例:spa
MSB | 补码 | 补码取反 | 补码取反加一 | 补码取反加一后所表明十进制值 | 符号 | 十进制结果 | 补码 |
---|---|---|---|---|---|---|---|
1 | 1111 1111 | 0000 0000 | 0000 0001 | 1 | - | -1 | 1111 1111 |
1 | 1111 1110 | 0000 0001 | 0000 0010 | 2 | - | -2 | 1111 1110 |
1 | 1111 1101 | 0000 0010 | 0000 0011 | 3 | - | -3 | 1111 1101 |
1 | 1111 1100 | 0000 0011 | 0000 0100 | 4 | - | -4 | 1111 1100 |
1 | 1111 1011 | 0000 0100 | 0000 0101 | 5 | - | -5 | 1111 1011 |
不管是正数加正数(十进制加法)仍是正数/负数加负数(十进制减法)均可以用补码加补码表示
正数加正数的补码计算过程示例:.net
表达式 | 补码相加 | 二进制结果 | 十进制结果 |
---|---|---|---|
0+0 | 0000 0000 + 0000 0000 —————— 0000 0000 |
0000 0000 | 0 |
0+1 | 0000 0000 + 0000 0001 —————— 0000 0001 |
0000 0001 | 1 |
1+1 | 0000 0001 + 0000 0001 —————— 0000 0010 |
0000 0010 | 2 |
2+1 | 0000 0010 + 0000 0001 —————— 0000 0011 |
0000 0011 | 3 |
正数加负数的补码计算过程示例:
表达式 | 补码相加 | 二进制结果 | 十进制结果 |
---|---|---|---|
0+(-0) | 0000 0000 + 0000 0000 —————— 0000 0000 |
0000 0000 | 0 |
0+(-1) | 0000 0000 + 1111 1111 —————— 1111 1111 |
1111 1111 | -1 |
1+(-1) | 0000 0001 + 1111 1111 —————— 1,0000 0000 |
0000 0000 | 0 |
1+(-2) | 0000 0001 + 1111 1110 —————— 1111 1111 |
1111 1111 | -1 |
2+(-2) | 0000 0010 + 1111 1110 —————— 1,0000 0000 |
0000 0000 | 0 |
2+(-1) | 0000 0010 + 1111 1111 —————— 1,0000 0001 |
0000 0001 | 1 |
负数加负数的补码计算过程示例:
表达式 | 补码相加 | 二进制结果 | 十进制结果 |
---|---|---|---|
(-0)+(-0) | 0000 0000 + 0000 0000 —————— 0000 0000 |
0000 0000 | 0 |
(-1)+(-1) | 1111 1111 + 1111 1111 —————— 1,1111 1110 |
1111 1110 | -2 |
(-1)+(-2) | 1111 1111 + 1111 1110 —————— 1,1111,1101 |
1111 1101 | -3 |
一样的一串二进制数字,便可以是反码也能够是补码,若是是补码则其能够经过上述规则转成对应的十进制数值,若是是反码则表明其为计算过程当中间值,若是想知道反码在十进制中所表示的数值,能够将其视为补码再经过上述规则转成十进制便可。
正数示例:
十进制数值 x | x 取补码 fn1(x)=a | x 取反码 fn2(x)=b | b 的十进制形式 y |
---|---|---|---|
0 | 0000 0000 | 1111 1111 | -1 |
1 | 0000 0001 | 1111 1110 | -2 |
2 | 0000 0010 | 1111 1101 | -3 |
3 | 0000 0011 | 1111 1100 | -4 |
4 | 0000 0100 | 1111 1011 | -5 |
负数示例:
十进制数值 x | x 取补码 fn1(x)=a | x 取反码 fn2(x)=b | b 的十进制形式 y |
---|---|---|---|
-0 | 0000 0000 | 1111 1111 | -1 |
-1 | 1111 1111 | 0000 0000 | 0 |
-2 | 1111 1110 | 0000 0001 | 1 |
-3 | 1111 1101 | 0000 0010 | 2 |
示例汇总:
十进制数值 x | x 取补码 fn1(x)=a | x 取反码 fn2(x)=b | b 的十进制形式 y | y + 1 | 十进制数值 x |
---|---|---|---|---|---|
0 | 0000 0000 | 1111 1111 | -1 | 0 | 0 |
1 | 0000 0001 | 1111 1110 | -2 | -1 | 1 |
2 | 0000 0010 | 1111 1101 | -3 | -2 | 2 |
3 | 0000 0011 | 1111 1100 | -4 | -3 | 3 |
-0 | 0000 0000 | 1111 1111 | -1 | 0 | -0 |
-1 | 1111 1111 | 0000 0000 | 0 | 1 | -1 |
-2 | 1111 1110 | 0000 0001 | 1 | 2 | -2 |
-3 | 1111 1101 | 0000 0010 | 2 | 3 | -3 |
经过该表格示例能够得出如下两个规律:
反码所表示的数值与原数值之间规律以下(y 表明反码以后的十进制值):
即若是想获得一个十进制正数值的负数形式(1 => -1)或则获得一个十进制负数值的正数形式能够经过对原值取反码加一获得:
十进制数值 x | 十进制取反 -x | 过程 |
---|---|---|
0 | 0 | 取反码(0)+1 = -1+1 |
1 | -1 | 取反码(1)+1 = -2+1 |
2 | -2 | 取反码(2)+1 = -3+1 |
3 | -3 | 取反码(3)+1 = -4+1 |
-1 | 1 | 取反码(-1)+1 = 0+1 |
-2 | 2 | 取反码(-2)+1 = 1+1 |
-3 | 3 | 取反码(-3)+1 = 2+1 |
将示例汇总表格再进一步简化:
十进制数值 x | x 的反码十进制表示形式 y | 翻译 -1 | 翻译 -2 |
---|---|---|---|
0 | -1 | 0 的反码是 -1 | -1 是 0 的反码 |
1 | -2 | 1 的反码是 -2 | -2 是 1 的反码 |
2 | -3 | 2 的反码是 -3 | -3 是 2 的反码 |
3 | -4 | 3 的反码是 -4 | -4 是 3 的反码 |
-0 | -1 | -0 的反码是 -1 | -1 是 -0 的反码 |
-1 | 0 | -1 的反码是 0 | 0 是 -1 的反码 |
-2 | 1 | -2 的反码是 1 | 1 是 -2 的反码 |
-3 | 2 | -3 的反码是 2 | 2 是 -3 的反码 |
能够看出在十进制格式下,原数值与反码的关系:
规律:
在 Go 语言中,一个数值是正数或负数,不管是何种打印方式,输出的都会待上正负号:
fmt.Printf("1 的十进制 : %v\n",1) fmt.Printf("-1 的十进制 : %v\n",-1) fmt.Printf("-1 的二进制(简化版): %v\n",strconv.FormatInt(-1,2)) fmt.Printf("1 的二进制 : %064b\n",1) // 占 64 位宽,不足补 0 fmt.Printf("-1 的二进制 : %064b\n",-1) // 占 64 位宽,不足补 0 fmt.Printf("4 的十进制 : %v\n",4) fmt.Printf("-4 的十进制 : %v\n",-4) fmt.Printf("-4 的二进制(简化版): %v\n",strconv.FormatInt(-4,2)) fmt.Printf("4 的二进制 : %064b\n",4) // 占 64 位宽,不足补 0 fmt.Printf("-4 的二进制 : %064b\n",-4) // 占 64 位宽,不足补 0 // 输出 // 1 的十进制 : 1 // -1 的十进制 : -1 // -1 的二进制(简化版): -1 // 1 的二进制 : 0000000000000000000000000000000000000000000000000000000000000001 // -1 的二进制 : -000000000000000000000000000000000000000000000000000000000000001 // 4 的十进制 : 4 // -4 的十进制 : -4 // -4 的二进制(简化版): -100 // 4 的二进制 : 0000000000000000000000000000000000000000000000000000000000000100 // -4 的二进制 : -000000000000000000000000000000000000000000000000000000000000100
若是咱们想要看到正确的负数的补码形式则须要经过无符号数值类型间接实现:
^uint8(1) + 1
;fmt.Printf("int8(1) : %08b \n", int8(1)) // 占 8 位宽,不足补 0 fmt.Printf("^int8(1) : %08b \n", ^int8(1)) // 占 8 位宽,不足补 0 fmt.Printf("^int8(1)+1 : %08b \n", ^int8(1)+1) // 占 8 位宽,不足补 0 fmt.Printf("uint8(1) : %08b \n", uint8(1)) // 占 8 位宽,不足补 0 fmt.Printf("^uint8(1) : %08b \n", ^uint8(1)) // 占 8 位宽,不足补 0 fmt.Printf("^uint8(1)+1 : %08b \n", ^uint8(1)+1)// 占 8 位宽,不足补 0 // 输出 // int8(1) : 00000001 // ^int8(1) : -0000010 // ^int8(1)+1 : -0000001 // uint8(1) : 00000001 // ^uint8(1) : 11111110 // ^uint8(1)+1 : 11111111
1111 1110
,接着对反码后加一获得原值 1 的负数形式 -1 的补码 1111 1111
;不管是有符号数值类型仍是无符号数值类型,只要转成补码后进行的计算过程不须要考虑符号位的问题。
A - B = A + ( -B )
A - B = 补码( A ) + 补码( -B )
原子操做便是进行过程当中不能被中断的操做。也就是说,针对某个值的原子操做在被进行的过程中,CPU 毫不会再去进行其它的针对该值的操做。不管这些其它的操做是否为原子操做都会是这样。为了实现这样的严谨性,原子操做仅会由一个独立的 CPU 指令表明和完成。只有这样才可以在并发环境下保证原子操做的绝对安全。
Go 语言提供的原子操做都是非侵入式的。它们由标准库代码包 sync/atomic 中的众多函数表明。咱们能够经过调用这些函数对几种简单的类型的值进行原子操做。这些类型包括 int3二、int6四、uint3二、uint6四、uintptr 和 unsafe.Pointer 类型,共 6 个。这些函数提供的原子操做共有 5 种,即:增或减、比较并交换、载入、存储和交换。
相关文档如图所示:
这里主要须要注意的是 Uint 类型的原子操做,以 AddUint32 函数为例
原子性的增长数值:
value := uint32(1) atomic.AddUint32(&value, 1) fmt.Printf("after call atomic.AddUint32(&value, 1) value is: %v\n", value) atomic.AddUint32(&value, 2) fmt.Printf("after call atomic.AddUint32(&value, 2) value is: %v\n", value) atomic.AddUint32(&value, 3) fmt.Printf("after call atomic.AddUint32(&value, 3) value is: %v\n", value) // 输出 // after call atomic.AddUint32(&value, 1) value is: 2 // after call atomic.AddUint32(&value, 2) value is: 4 // after call atomic.AddUint32(&value, 3) value is: 7
原子性的减小数值:
如文档所述,若是须要减去一个正数 c 须要经过 ^uint32(c-1)
计算获得 c 的补码。
const one, two, three = 1, 2, 3 value := uint32(10) atomic.AddUint32(&value, ^uint32(one - 1)) // 减一 fmt.Printf("after callatomic.AddUint32(&value, ^uint32(one - 1)) value is: %v\n", value) atomic.AddUint32(&value, ^uint32(two - 1)) // 减二 fmt.Printf("after callatomic.AddUint32(&value, ^uint32(two - 1)) value is: %v\n", value) atomic.AddUint32(&value, ^uint32(three - 1)) // 减三 fmt.Printf("after callatomic.AddUint32(&value, ^uint32(three - 1)) value is: %v\n", value) // 输出 // after callatomic.AddUint32(&value, ^uint32(one - 1)) value is: 9 // after callatomic.AddUint32(&value, ^uint32(two - 1)) value is: 7 // after callatomic.AddUint32(&value, ^uint32(three - 1)) value is: 4
^
;