Go语言精度丢失问题

[toc]

问题描述

在使用Go做项目的时候,一定要注意Go语言的精度丢失问题,如下:

[root@mail golang]# cat gofloat.go 
/*************************************************************************
    > File Name: gofloat.go
    > Author: smile
    > Mail: 3293172751nss@gmail.com 
    > Created Time: Wed 31 Aug 2022 05:43:18 PM CST
 ************************************************************************/
package main
import(
    "fmt"
)

func main(){

    //Go语言精度丢失问题
    m1 := 8.3
    m2 := 3.8
    fmt.Println("m1 - m2 = ",m1 - m2)
}
[root@mail golang]# go run  gofloat.go
m1 - m2 =  4.500000000000001

可以看到输出的m1 - m2 = 4.500000000000001

为什么不直接使用 float64?

因为 float64(或任何二进制浮点类型,实际上)不能0.1准确地表示数字。

考虑以下代码:http://play.golang.org/p/TQBd4yJe6B您可能期望它打印出来10,但实际上打印出来9.999999999999831。随着时间的推移,这些小错误真的会加起来!

为什么不直接使用 big.Rat?

为什么不直接使用big.Rat?

big.Rat 适合表示有理数,但 Decimal 更适合表示金钱。为什么?这是一个(人为的)示例:

假设你使用 big.Rat,你有两个数字,x 和 y,都代表 1/3,你有z = 1 - x - y = 1/3. 如果将每一个都打印出来,则字符串输出必须在某处停止(为简单起见,假设它停止在 3 个十进制数字处),因此您将得到 0.333、0.333 和 0.333。但是另一个 0.001 去哪儿了?

这是上面的示例代码:http 😕/play.golang.org/p/lCZZs0w9KEopen in new window

使用 Decimal,打印出来的字符串准确地表示数字。所以,如果你有x = y = 1/3(精度为 3),它们实际上将等于 0.333,当你这样做时z = 1 - x - y,它们z将等于 0.334。没有钱是下落不明的!

你还是要小心。如果您想以N3 种方式拆分数字,则不能只发送N/3给三个不同的人。你必须选择一个发送 N - (2/3*N)到。该人将获得一分钱的剩余部分。

但是,使用 Decimal 比使用 big.Rat 更容易小心。

出现原因总结

  • 出现浮点数不精确的原因是,浮点数储存至内存中时,2的-1、-2……-n次方不能精确表示小数部分,所以再把这个数从地址中取出来进行计算就出现了偏差。

  • 不是所有的float相加减乘除都一定出现偏差,具体要根据golang实现IEEE 754的情况定。

解决方法

使用第三方包来解决精度丢失问题

用法

package main

import (
	"fmt"
	"github.com/shopspring/decimal"
)

func main() {
	price, err := decimal.NewFromString("136.02")
	if err != nil {
		panic(err)
	}

	quantity := decimal.NewFromInt(3)

	fee, _ := decimal.NewFromString(".035")
	taxRate, _ := decimal.NewFromString(".08875")

	subtotal := price.Mul(quantity)

	preTax := subtotal.Mul(fee.Add(decimal.NewFromFloat(1)))

	total := preTax.Mul(taxRate.Add(decimal.NewFromFloat(1)))

	fmt.Println("Subtotal:", subtotal)                      // Subtotal: 408.06
	fmt.Println("Pre-tax:", preTax)                         // Pre-tax: 422.3421
	fmt.Println("Taxes:", total.Sub(preTax))                // Taxes: 37.482861375
	fmt.Println("Total:", total)                            // Total: 459.824961375
	fmt.Println("Tax rate:", total.Sub(preTax).Div(preTax)) // Tax rate: 0.08875
}

完美解决

/*
 * @Author: xiongxinwei 3293172751nss@gmail.com
 * @Date: 2022-08-31 17:58:18
 * @LastEditors: xiongxinwei 3293172751nss@gmail.com
 * @LastEditTime: 2022-08-31 17:58:35
 * @FilePath: \code\gofloat\gofloat.go
 * @Description: Go语言精度丢失问题
 */
 package main

 import (
	 "fmt"
 
	 "github.com/shopspring/decimal"
 )
 
 func main() {
 
	 //Go语言精度丢失问题
	 m1 := 8.3
	 m2 := 3.8
	 fmt.Println("m1 - m2 = ", m1-m2)
 
	 var num1 float64 = 3.1
	 var num2 float64 = 4.2
	 d1 := decimal.NewFromFloat(num1).Add(decimal.NewFromFloat(num2))
	 fmt.Println("num1 - num2 = ", d1)
 }
 

编译:

PS D:\文档\git\Block_Chain\go-advancend\code\gofloat> go run .\gofloat.go
m1 - m2 =  4.500000000000001
num1 - num2 =  7.3