当前位置: 首页 > 科技观察

无符号整数操作注意事项

时间:2023-03-17 23:17:22 科技观察

在很多强类型编程语言中,都有一种特殊的类型——无符号整数类型。如果在使用过程中不注意,这种数据类型往往会导致意想不到的错误。至于需要了解的注意事项和知识点,一起来看看吧。1.go源码中的数据类型//go源码位置:src/math/const.go////Integerlimitvalues.const(MaxInt8=1<<7-1MinInt8=-1<<7MaxInt16=1<<15-1MinInt16=-1<<15MaxInt32=1<<31-1MinInt32=-1<<31MaxInt64=1<<63-1MinInt64=-1<<63MaxUint8=1<<8-1MaxUint16=1<<16-1MaxUint32=1<<32-1MaxUint64=1<<64-1)//转源码位置:src/builtin/builtin.go////uint8isthesetofallunsigned8-bitintegers.//Range:0through255.typeuint8uint8//uint16isthesetofallunsigned16-bitintegers.//Range:0through65535.typeuint16uint16//uint32isthesetofallunsigned32-bitintegers.//Range:0through4294967295.typeuint32uint32//uint64isthesetofallunsigned64-bitintegers.//Range:0through18446744073709551615.typeuint64uint64//int8isthesetofallsigned8-bitintegers.//Range:-128through127.typeint8int8//int16isthesetofallsigned16-bitintegers.//Range:-32768through32767.typeint16int16//int32isthesetofallsigned32-bitintegers.//Range:-2147483648through2147483647.typeint32int32//int64isthesetofallsigned64-bitintegers.//Range:-9223372036854775808through9223372036854775807.typeint64int64//float32isthesetofallIEEE-75432-bitfloating-pointnumbers.typefloat32float32//float64isthesetofallIEEE-75464-bitfloating-pointnumbers.typefloat64float64从源码中可以看出:无符号类型只有正数值域(最小值为0),没有负值字段。有符号类型具有正值和负值字段。uint8,正值范围0~255,共256个值Signedtypeint8,正值范围0~127,共128个值。2、无符号类型和有符号类型的取值范围你可能会问:为什么相同长度的无符号和有符号类型的取值范围是这样分布的?看过计算机微机原理的同学大概知道一点,明白其中的道理:计算机只知道0和1,而0和1中的每一个都叫做位(位是计算机中最小的单位)。计算机系统中所有的值和数据都是用0和1的字符串组合来存储不同的0、1组合,由于使用了不同的编码和解码方式,被赋予了不同的含义,进而具有了各种数据类型。取无符号类型uint8和有符号类型int8,长度为8位:uint8unsignedtypeint8有符号类型的8位中,只有最后7位用来表示值。剩下的位用来表示符号位,0表示正值,1表示负值。在二进制中,1位长度的不同造成的表达式取值范围是3的两倍。unsigned类型和signed类型的加减先看一段代码:funcdemo(){varauint8=1varbuint8=2v1:=a-bfmt.Println("uint81-2=",v1)varcint8=1vardint8=2v2:=c-dfmt.Println("int81-2=",v2)fmt.Println("---------------")vareuint8=math.MaxUint8varfuint8=1v3:=e+ffmt.Printf("uint8255+1=%d%T\n",v3,v3)vargint8=math.MaxInt8varfuint8=1v4:=g+hfmt.Printf("int8127+1=%d%T\n",v4,v4)}聪明的你,猜猜执行结果会是什么?uint8-v11-2=255int8-v21-2=-1----------------uint8-v3255+1=0uint8int8-v4127+1=-128int8结果分析:v3和v4是in在进行加法运算时,由于运算结果超出了对应的数值位长度,导致溢出的数据位无效,从而导致溢出。v2结果正确。Bug在网上看到一个关于无符号整数减法的bug,如下图:4.关于无符号整数加减法的一些结论首先是关于无符号整数加减法的一些结论:1.无符号整数相加时,与其他类型一样,当运算结果超过位数时,就会发生溢出。2、无符号整数相减时,运算结果有两种情况。2.1减数>=被减数,最后的结果大于等于02.2减数<被减数,最后的结果也大于等于0。无符号整数的减法比较特殊。当减数小于被数时,结果也大于等于0,是不是很奇怪?一句话总结:无符号数的加减运算,结果永远不会小于0。5.无符号类型值减法问题[root@localhostworkspace]#cat-nt.go1packagemain23import"fmt"45funcmain(){6varauint8=17varbuint8=28v1:=a-b910fmt.Println("uint8-v11-2=",v1)11}第八行代码进行两次uint8无符号类型减法运算,得到结果v1。[root@localhostworkspace]#gobuild-gcflags="-N-l-S"t.go#command-line-arguments"".mainSTEXTsize=222args=0x0locals=0x80funcid=0x00x000000000(/root/workspace/t.go:5)TEXT"".main(SB),ABIInternal,$128-0......0x002b00043(/root/workspace/t.go:8)MOVBLZX"".a+54(SP),AX0x003000048(/root/workspace/t.go:8)go:8)ADDL$-2,AX//!!!add-20x003300051(/root/workspace/t.go:8)MOVBAL,"".v1+52(SP)0x003700055(/root/workspace/t.go:10)MOVBAL,""..autotmp_3+55(SP)首先要说明一件事:电脑里没有减法,只有加法(没想到)。从汇编代码可以看出,a-b转化为a+(-b),即1-2=1+(-2)。按理说1-2应该等于-1,这里发生了什么?6、负数的表示形式——补码前面说过,计算机只承认二进制0、1、2属于十进制值,十进制可以看作是计算机上的编解码规则。那么,它对应的二进制文件是什么?funcdemo2(){vara,b,cuint8a=1b=2c=a+(-b)fmt.Printf("a的二进制是:%08b\n"+"b的二进制是:%08b\n"+"-b在binary:%08b\n"+"cinbinary:%08b\n"+"cindecimal:%d",a,b,-b,c,c,)}执行结果为:a的二进制为:00000001b是:00000010-b是二进制:11111110c是:11111111c是十进制:255在计算机系统中,数值的编码有三种:原码、反码、补码。反码和补码一般用于负数。反码=负数对应正数原码的反码,补码=反码+1。正数的原码、反码、补码是一样的。负数分为两种情况:3.1对于有符号类型:负数的取值位用补码表示,符号位设为13.2对于无符号类型:负数的取值位用补码表示,并且由于无符号位,因此无需设置符号位。从上面的规则可以看出:uint8类型的-b对应二进制为11111110,uint8类型a对应的二进制为00000001c=a+(-b),则对应的位加法为11111111。结果为同类型相加还是同类型,所以c还是uint8,因为unsigned类型的取值范围内没有负数字段,所以11111111转为十进制就是2557。有些疑惑,是不是和我一样,有疑惑?无符号类型可以分配负数吗?你可能会问:既然无符号类型总是不是负数,那能不能给它赋负数呢?funcdemo3(){varauint8a=-2fmt.Println(a)}执行结果:#command-line-arguments./main.go:59:4:constant-2overflowsuint8passed报错信息可以看出无法赋值负数到无符号类型。unsigned类型不能赋负数,为什么可以取负数?既然unsigned类型不能赋负数,那为什么unsigned类型可以取负数呢?funcdemo3(){varauint8a=2fmt.Println(-a)}mayyou我再问一下:-a需要和a的类型保持一致。-a不能表示为无符号类型。为什么没有报错?[root@localhostworkspace]#cat-nt.go1packagemain23import"fmt"45funcmain(){6vara1uint87a1=289fmt.Printf("%b\n",-a1)10}[root@localhostworkspace]#gobuild-gcflags="-N-l-S"t.go#command-line-arguments"".mainSTEXTsize=197args=0x0locals=0x80funcid=0x00x000000000(/root/workspace/t.go:5)TEXT"".main(SB),ABIInternal,$128-0......0x002b00043(/root/workspace/t.go:9)MOVB$-2,"“..autotmp_1+71(SP)0x003000048(/root/workspace/t.go:9)XORPSX0,X00x003300051(/root/workspace/t.go:9)MOVUPSX0,”..autotmp_2+80(SP)0x003800056(/root/workspace/t.go:9)LEAQ"..autotmp_2+80(SP),AX0x003d00061(/root/workspace/t.go:9)MOVQAX,""..autotmp_4+72(SP)0x004200066(/root/workspace/t.go:9)TESTBAL,(AX)0x004400068(/root/workspace/t.go:9)MOVBLZX""..autotmp_1+71(SP),CX0x004900073(/root/workspace/t.go:9)LEAQtype.uint8(SB),DX//!!!type.uint8对-2进行类型转换0x005000080(/root/workspace/t.go:9)MOVQDX,“”..autotmp_2+80(SP)0x005500085(/root/workspace/t.go:9)LEAQruntime.staticuint64s(SB),DX0x005c00092(/root/workspace/t.go:9)LEAQ(DX)(CX*8),CX0x006000096(/root/workspace/t.go:9)MOVQCX,""..autotmp_2+88(SP)0x006500101(/root/workspace/t.go:9)TESTBAL,(AX)0x006700103(/root/workspace/t.go:9)JMP1050x006900105(/root/workspace/t.go:9)MOVQAX,""..autotmp_3+96(SP)0x006e00110(/root/workspace/t.go:9)MOVQ$1,""..autotmp_3+104(SP)0x007700119(/root/workspace/t.go:9)MOVQ$1,""..autotmp_3+112(SP)0x008000128(/root/workspace/t.go:9)LEAQgo.string."%b\n"(SB),CX0x008700135(/root/workspace/t.go:9)MOVQCX,(SP)0x008b00139(/root/workspace/t.go:9)MOVQ$3,8(SP)0x009400148(/root/workspace/t.go:9)MOVQAX,16(SP)0x009900153(/root/workspace/t.go:9)MOVQ$1,24(SP)0x00a200162(/root/workspace/t.go:9)MOVQ$1,32(SP)0x00ab00171(/root/workspace/t.go:9)PCDATA$1,$00x00ab00171(/root/workspace/t.go:9)CALLfmt.Printf(SB)...可以通过汇编看到,通过类型.uint8(SB),DX对运算结果进行类型转换。因此,我们可以得出结论:-a是a和减号(-)的运算,运算结果的最终类型会被转换为与a一致。小结本文通过几个例子展示了无符号类型和有符号类型的区别和注意事项。那么,什么时候使用无符号类型,什么时候使用有符号类型呢?如果希望运算结果包含负数,则不能使用无符号类型。这时候最好使用有符号的类型。值字段足够大。这时候最好使用无符号类型。不使用unsigned类型就可以少用unsigned类型,减少bug!!!其他特殊场景,如:在go语言运行时GPM的逻辑处理器P结构上,P使用unsigned类型存储goroutine本地队列的头尾位置。typepstruct{...//Queueofrunnablegoroutines.Accessedwithoutlock.runqheaduint32//本地运行队列头位置runqtailuint32//本地运行队列尾位置runq[256]guintptr//每个P可以有256个G.....}//runqputtriestoputsgonthelocalrunnablequeue.//如果nextifalse,runqputaddsgtothetailoftherunnablequeue。//如果next为真,runqputsginthe_p_.runnextslot。//如果runqueue满了,runnextputsgontheglobalqueue。//ExecutedonlybytheownerP。/放入G。如果next为true,则放入下一个。否则它被附加到队列的末尾。如果队列已满,则放入全局队列。funcrunqput(_p_*p,gp*g,nextbool){......h:=atomic.Load(&_p_.runqhead)//load-acquire,synchronizewithconsumerst:=_p_.runqtail//放入本地队列ift-h