Golang中的精度丢失问题及解决方案

背景:

the actual total amount of batch 1191 is 58508228.05 but the Total Transaction Amount shows in admin is 58508228

数据库存储amount这个字段用的数据类型是decimal(20,4),现在需要计算的是sum(amount),并将其取出显示在admin上,由于sum(amount)的值可能较大,而golang语言中没有decimal这个数据类型,因此必然涉及到数据类型转换问题

bug来源:

一开始的思路:decimal → string → float32

record.Amount, err = strconv.ParseFloat(string(v["sum(amount)"]), 32)

浮点类型是不精确的类型,即便是float64也无法精确反映数据本身

原因:对于二进制小数,小数点右边能表达的值是 1/2, 1/4, 1/8, 1/16, 1/32, 1/64, 1/128 … 1/(2^n),也就是说计算机只能用这些 1/(2^n)之和来表达十进制的小数

例如:计算机表达十进制的 0.2 :

0.01 = 1/4 = 0.25 ,太大

0.001 =1/8 = 0.125 , 又太小

0.0011 = 1/8 + 1/16 = 0.1875 , 逼近0.2了

0.00111 = 1/8 + 1/16 + 1/32 = 0.21875 , 又大了

0.001101 = 1/8+ 1/16 + 1/64 = 0.203125 还是大

0.0011001 = 1/8 + 1/16 + 1/128 = 0.1953125 这结果不错

0.00110011 = 1/8+1/16+1/128+1/256 = 0.19921875
已经很逼近了, 就这样吧

而float32和float64的区别就在于,计算机提供的二进制位数不同,因为计算机不可能无限逼近,那么位数的作用就是在有限的位数内不断逼近十进制数

解决方案:

为了提供精确的值,最后选择"github.com/shopspring/decimal"这个包,decimal.NewFromString之所以可以精确转换小数,是因为它结合了big.int以及进制的巧妙精准转换

代码示例:

package main
 
import (
    "fmt"
    "github.com/shopspring/decimal"
    "strconv"
)
 
func main() {
    test01, err := strconv.ParseFloat("3840629.82", 32)
    test02, err := strconv.ParseFloat("3840629.82", 64)
    test03, err := decimal.NewFromString("3840629.82")
    test04, _ := test03.Float64()
 
    if err != nil {
        panic(err)
 
    }
 
    fmt.Printf("%f\n", test01)
    fmt.Printf("%f\n", test02)
    fmt.Printf("%v\n", test03)
    fmt.Printf("%f\n", test04)
 
}
 
 
/* 输出:
3840629.750000
3840629.820000
3840629.82
3840629.820000
*/

参考:Golang 笔记之深入浮点数_weishixianglian的博客-CSDN博客_golang 浮点数

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值