最近fix一个奇怪的bug,这个bug源于是为了解决商品价格在计算的时候因为有小数,在计算的时候由于存在精度丢失所以选择了乘 100 以分单位进行计算。但是在golang中 float64类型的 13.8 ✖️ 100 !=1380
在遇到这个bug之前。我一度以为浮点数的精度丢失问题,在保留2位小数的情况下,乘100变成整数再进行加减计算就不会丢精度,但事实是,乘法本身算出来的数就已经走样了。
浮点数如何存储的-IEEE-754
浮点数数据类型主要有:单精度float32、双精度float64
IEEE-754 标准是一个浮点数标准,存在 32、64、128 bit 三种格式(上面两幅图分别是 32 bit 和 64 bit 的情况,结构是一致的)
IEEE-754 标准将 64 位分为三部分:
- sign,1 bit 的标识位,0 为正数,1 为负数
- exponent,指数,11 bit
- fraction,小数部分,52 bit
什么是精度丢失
计算机在处理数据都涉及到数据的转换和各种复杂运算,比如,不同单位换算,不同进制(如二进制十进制)换算等,很多除法运算不能除尽,比如10÷3=3.3333…..无穷无尽,而精度是有限的,3.3333333x3并不等于10,经过复杂的处理后得到的十进制数据并不精确,精度越高越精确。float32和float64的精度是由尾数的位数来决定的,其整数部分始终是一个隐含着的“1”,由于它是不变的,故不能对精度造成影响。float32:2^23 = 8388608,一共七位,由于最左为1的一位省略了,这意味着最多能表示8位数:28388608 = 16777216 。有8位有效数字,但绝对能保证的为7位,也即float32的精度为78位有效数字;float64:2^52 = 4503599627370496,一共16位,同理,float64的精度为1617位。
所谓的精度丢失就是无限循环小数在计算机怎么存储?计算机再大的内存它也存不下,对吧! 所以不能存储一个相对于数学来说的值,只能存储一个近似值,所以当计算机存储后再取出来用时就会出现精度问题。
Golang的解决方案
使用第三方库 decimal
运行:go get github.com/shopspring/decimal
在运算开始前,设置你需要的精确的小数位数,自动四舍五入
decimal.DivisionPrecision = 2 // 保留两位小数,如有更多位,则进行四舍五入保留两位小数
1 | // 1.2+3 float和int相加 |