首页 > 其他分享 >(转)二进制与 Go 的原子操作

(转)二进制与 Go 的原子操作

时间:2023-02-28 09:35:59浏览次数:47  
标签:0000 二进制 补码 value 原子 1111 Go 反码 十进制

原文:https://segmentfault.com/a/1190000039769367

前置阅读:

二进制相关基础概念

有符号二进制整数有正数和负数。在 x86 处理器中,MSB 表示的是符号位:0 表示正数,1 表示负数。下图展示了 8 位的正数和负数:

image

概念总结:

  • 反码、补码是二进制的一种表现形式;
  • 在计算机内所有数值底层都用补码表示,无论正负数(十进制);
  • 如果一串二进制值需要视为数值则需要将其视为补码;
  • 反码是十进制转二进制计算的一个过程即对一个十进制取补码的过程,一般用在负数转换规则上;
  • 反码可以通过二进制值按位取反得到(所有二进制位都取反);
  • 正数(十进制)的补码是其二进制本身,负数(十进制)的补码是十进制负数的绝对值求补码后取反码加一;
  • 表示正数的补码可以直接转成十进制,表示负数的补码想要转回十进制步骤如下:

    • 对表示负数的补码取反码加一得到负数的十进制绝对值补码;
    • 再将负数的十进制绝对值补码转成十进制得到负数的十进制绝对值;
    • 最后加上符号位;
  • 无论是正数加正数(十进制加法)还是正数/负数加负数(十进制减法)都可以用补码加补码表示;
  • 一个值的正数的补码与其负数的补码相加等于 0;

反码

反码可以通过二进制值按位取反得到(所有二进制位都取反)

正数的反码示例:

十进制数值补码反码
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

负数补码计算过程示例:

十进制数值绝对值绝对值补码绝对值补码取反绝对值补码取反加一正确补码十进制数值
-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

补码(二进制转十进制)

表示正数的补码可以直接转成十进制,表示负数的补码想要转回十进制步骤如下:

  • 对表示负数的补码取反码加一得到负数的十进制绝对值补码;
  • 再将负数的十进制绝对值补码转成十进制得到负数的十进制绝对值;
  • 最后加上符号位;
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

负数转换示例:

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

补码相加

无论是正数加正数(十进制加法)还是正数/负数加负数(十进制减法)都可以用补码加补码表示

正数加正数的补码计算过程示例:

表达式补码相加二进制结果十进制结果
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

二进制、反码、补码

同样的一串二进制数字,即可以是反码也可以是补码,如果是补码则其可以通过上述规则转成对应的十进制数值,如果是反码则代表其为计算过程中间值,如果想知道反码在十进制中所表示的数值,可以将其视为补码再通过上述规则转成十进制即可。

正数示例:

十进制数值 xx 取补码 fn1(x)=ax 取反码 fn2(x)=bb 的十进制形式 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

负数示例:

十进制数值 xx 取补码 fn1(x)=ax 取反码 fn2(x)=bb 的十进制形式 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

示例汇总:

十进制数值 xx 取补码 fn1(x)=ax 取反码 fn2(x)=bb 的十进制形式 yy + 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 代表反码之后的十进制值):

  • fn2(x) = -x-1
  • fn2(x) + 1 = -x
  • y = -x-1
  • y +1 = -x

即如果想得到一个十进制正数值的负数形式(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

规律 二

将示例汇总表格再进一步简化:

十进制数值 xx 的反码十进制表示形式 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 的反码

可以看出在十进制格式下,原数值与反码的关系:

  • 如果我需要 -1 我可以用 0 的反码代替;
  • 如果我需要 -4 我可以用 3 的反码代替;
  • 规律:

    • x = |y| -1
    • x + y = -1

Go 的表现

二进制的输出格式

在 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
  • 可以看出输出二进制时 Go 的输出与十进制一样同样将符号位具象化,而非输出对应的 0 或 1;
  • 输出二进制时数值部分则取其绝对值的补码;

如果我们想要看到正确的负数的补码形式则需要通过无符号数值类型间接实现:

  • 无符号数值类型如何表示一个数的负数形式,答案是补码取反码加一;
  • 譬如如何用无符号数值类型表示 -1: ^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 
  • 可以看到通过使用无符号数值类型对 1 取反后得到的是 -2 的补码形式 1111 1110,接着对反码后加一得到原值 1 的负数形式 -1 的补码 1111 1111

Go 的原子操作

无论是有符号数值类型还是无符号数值类型,只要转成补码后进行的计算过程不需要考虑符号位的问题。

A - B = A + ( -B )

A - B = 补码( A ) + 补码( -B )

原子操作即是进行过程中不能被中断的操作。也就是说,针对某个值的原子操作在被进行的过程当中,CPU 绝不会再去进行其它的针对该值的操作。无论这些其它的操作是否为原子操作都会是这样。为了实现这样的严谨性,原子操作仅会由一个独立的 CPU 指令代表和完成。只有这样才能够在并发环境下保证原子操作的绝对安全。

Go 语言提供的原子操作都是非侵入式的。它们由标准库代码包 sync/atomic 中的众多函数代表。我们可以通过调用这些函数对几种简单的类型的值进行原子操作。这些类型包括 int32、int64、uint32、uint64、uintptr 和 unsafe.Pointer 类型,共 6 个。这些函数提供的原子操作共有 5 种,即:增或减、比较并交换、载入、存储和交换。

原子操作 - 增或减

相关文档如图所示:

image.png

这里主要需要注意的是 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
  • value -1 等价于 value + (-1) ,等价于 补码( value ) + 补码( -1 );
  • 通过前面二进制规律二 得知,求 -1 的补码相当于求 0 的反码;
  • go 的反码运算符位 ^
  • 结合后便可实现了无符号类型数据的减法运算。

标签:0000,二进制,补码,value,原子,1111,Go,反码,十进制
From: https://www.cnblogs.com/liujiacai/p/17162766.html

相关文章

  • Golang如何快速构建一个CLI小工示例
    这篇文章主要为大家介绍了Golang如何快速构建一个CLI小工具详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪如何Golang快速构建一个CLI......
  • golang对接企业微信群机器人-在线客服系统新消息提醒方式之一【唯一客服】
    最近客服系统对接了一下企业微信的机器人企业成员(内部)群机器人只能在企业微信内部群里添加,设置好机器人头像名称之后会得到一个webhook,创建者可使用此wenhook去调用相关a......
  • Django的配置文件介绍
    Django的配置文件#pathlib#3.6以后,处理文件路径的模块,原来是os,#面试题,md5是对称加密还是非对称加密 -对称加密:加密的秘钥和解密的秘钥是同一个-非对称加......
  • Golang入门第四天
    面向对象编程匿名字段匿名字段初始化方法值语义与引用语义封装,继承,多态方法值,方法表达式接口接口继承,接口转换空接口通过if实现类型断言通过switch实现类型断......
  • Go语言入门学习
    一.Go语言介绍Go是一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易。Go是从2007年末由RobertGriesemer,RobPike,KenThompson主持开发,后来还加入了Ian......
  • 路飞项目,后台日志封装,全局异常处理,封装response,luffy数据库创建,软件开发模式,user模块
    内容回顾虚拟环境两个模块virtualenv—>virtualenv.exe用来创建虚拟环境virtualenvwrapper-win—>virtualenvwrapper.bat批处理文件,命令简化命令:创建mkvirtualev......
  • Go 语言推荐书籍(2023)
    Go是谷歌公司为了解决重大问题而设计的一种小型编程语言。快速、现代的编程语言能让业余爱好者、初学者和专业人员都受益。你需要的正是这样的语言。今天给大家推荐10余本......
  • golang 实现链表爽不爽?
    犹记得刚学C语言的时候,学到指针这一章,就会有让我们写链表的需求,头插法,尾插法,翻转链表,合并链表,约瑟夫环等等学的不亦乐乎,但是对于指针刚学的时候,真是摸不着脑壳,不知道x......
  • Django uwsgi问题解析
    通常情况下,部署Django应用到生产环境时都会通过uwsgi部署,uwsgi一些配置项配置问题有可能会导致服务出现502状态码或者其他超时等的情况常用到的配置项如下:reload-on-as......
  • 路飞之-后台日志封装-前后端分离的rbac项目演示-全局异常处理封装-封装Response-luffy
    目录路飞之-后台日志封装-前后端分离的rbac项目演示-全局异常处理封装-封装Response-luffy数据库创建-软件开发模式-User模块用户表-django的配置文件-开启media访问今日内......