当前位置: 首页 > Web前端 > JavaScript

go int64传递到前端导致溢出问题排查

时间:2023-03-27 01:54:18 JavaScript

goint64传给前端排查溢出问题,结果不一样。恰好我对前端javascript略有了解,然后连夜写了一些代码来探究这个问题。这个问题的本质是javascript数字类型可以表示的数据范围不能完全包含go中int64的范围。来看看作者怎么说。踩坑分析话不多说,我们用下面的代码搭建一个gohttp后端实验场景。下面的代码提供了gonativehttpapi和httpframeworkgin来启动http服务。读者可以通过这两种方式中的任意一种来运行服务。typeDatastruct{idint64`json:"id"`}//构建实??验数据。varint64Data=[]Data{{Id:1,},{Id:2,},{Id:3,},{Id:1<<53,},{Id:1<<53+1,},{Id:1<<53+2,},{Id:1<<53+3,},}//去原生http服务funcTestHttpJson(t*testing.T){varhttpJsonTestHandler=func(whttp.ResponseWriter,r*http.Request){log.Println("comhere")r.Header.Set("Access-Control-Allow-Origin","*")w.Header().Set("content-type","text/json")如果resByte,jsonErr:=json.Marshal(int64Data);jsonErr!=nil{w.Write([]byte(jsonErr.Error()))log.Fatal(jsonErr)}else{w.Write(resByte)}return}http.HandleFunc("/json_test",httpJsonTestHandler)错误:=http.ListenAndServe(":8888",nil)iferr!=nil{log.Fatal("ListenAndServe:",err)}select{}}//ginhttp服务funcTestJsonNumberWithGin(t*testing.T){r:=gin.Default()r.GET("json_test",func(c*gin.Context){c.Request.Header.Set("Access-Control-Allow-Origin","*")c.JSON(http.StatusOK,int64Data)})r.Run(":8888")}运行服务后,我们使用postman访问这个接口。好像没什么问题,一切正常的样子。但这只是表象。因为我们返回的是字符数组,postman读取的也是字符数组,所以相当于postman拿到字符数组拼接成字符串给我们看。这样无论你后端http接口返回什么,postman都可以给你做完全一样的呈现。同理,如果你在浏览器上访问这个接口,你是看不到数据有什么区别的。这其实是一个蒙眼。当我们看到postman上显示没有问题时,我们就会想当然的认为前端实际javasrcipt运行环境没有问题,从而忽略这个问题。但现实与理想之间往往存在差距。下面来看看javascript中的实际运行效果。这里笔者使用node.js访问http接口。如果机器本地没有node.js,建议安装。如果和笔者一样使用mac,执行如下命令安装:brewinstallnode如果是windows,去node官网(https://nodejs.org/en/download/)下载后安装即可它愚蠢地。在javascript中访问http接口,笔者这里使用了axios,这也是前端常用的http库。可以通过运行以下脚本安装:npminstallaxios下面是javascript访问http接口代码jsonNumberTest.js:constaxios=require('axios');axios.get('http://127.0.0.1:8888/json_test').then(res=>{const{data}=resconsole.info(data)}).catch(err=>{console.log(err.message)})通过运行以下命令执行此javascript程序script:nodejsonNumberTest.js看看结果吧。这里可以看出他的不同。对于这个数组的前三个数字,1、2、3没有区别,但是后面的数字就明显不同了。我们http接口中返回的四个数字分别是1<<53、1<<53+1、1<<53+2、1<<53+3,但是我们看到在nodejs中打印出来显然是不连续的.问题是,是什么原因造成的。事情的真相是,javascript中只有number的数据类型是浮点数,没有go中的基本数据类型丰富,也就是int,int32,int64,float32...,这些在go中与数字相关的数据类型,在js中只有一种数字类型与之对应。如果go中的数字超出了javascript数字可以表示的范围,就会出现上面的现象。再来看看javascript中的number类型,其实就是计算机中的浮点数表示。浮点数在计算机中的表示我们知道,在现实世界中,两位小数之间有无数的小数,但是计算机能够表示的数是有限的,所以浮点数在计算机中会存在一定的精度不足.也就是说,有些小数只能近似,不能准确表示。目前的编程语言大多采用国际标准IEEE754来存储浮点数。在IEEE754中,每个二进制表达式的值可以通过以下公式计算:$$V=(-1)^s*M*2^E$$公式中字母的含义和二进制表达式中的位置如下图所示。对于单精度浮点数,符号位占1位,指数位占8位,有效位占23位。对于双精度浮点数,符号位占1位,指数位占11位,有效位占52位。IEEE754规定,在规范化表示中,M应写成1.XXXXXXX的形式,其中XXXXXXX为小数部分。这里的1不用存,实际计算时加上即可。另外,在科学记数法中,E可以有负数,所以为了保证E的正负部分分布均匀,指数的值要减去所表示范围的中间值计算时的指数。这意味着什么?也就是说,如果用单精度浮点数表示,指数占8位,那么它的取值范围是[0,255],那么中间要减去的数就是127,那么如果exponentposition实际计算出来的值为10(十进制),那么exponent实际存储的值应该是10+127=137。同理,doubleprecision的取值范围是[0,2047],所以中间数是1023.下面举个例子,看看单精度浮点数6.625是怎么表示的。首先我们将这个数字转换为二进制。$$110.101$$然后我们进行归一化表示,也就是变成小数点前只有一个数的形式,由于有效位数是23位,所以不足的数要补0。$$1.10101000000000000000000*2^2$$所以这个数的符号位为0,有效数M为10101000000000000000000,指数E为2+127=129=100000001,则存储格式为这个数是:$$01000000110101000000000000000000$$双精度浮点数和单精度浮点数的存储原理是一样的,这里不再赘述。问题定位及改正方法通过上面对浮点数的描述,我们很容易知道双精度浮点数所能表示的最大整数值为:$$(-1)^0*\sum_{n=1}^{52}{2^n}$$表示有效位数全为1,指数为52(计算时去掉小数点,只保留整数)。这个值为2<<53-1。因此,goint64类型的数字通过http传递给前端时,大于2<<53-1的数字会因为溢出而无法正常表示。更好的方法解决这个问题的方法是将String传递给前端,而不是像int64那样传递危险的数字。如果这个数字对应的是数据库中一条记录的id,那么前端在完成操作后会将这个id返回给前端。当后端执行更新操作时,由于传递的id与原来的不同,其他记录会被更新,造成数据问题。总结发现和定位这个问题其实并不容易。一方面,问题其实发生在go数据传给js的时候。两个人在一起就不一样了。其次,要对计算机的底层知识有更深入的了解。这也从侧面反映出我们还有很多需要学习的地方。参考Why0.1+0.2isnotequalto0.3:https://draveness.me/whys-the...CSAPPChapter2:InformationRepresentationandProcessingPersonalPromotion以下是作者的公众号--《陪伴电脑》好久不见”,希望兄弟们多多关注,谢谢支持~