Go如何同Java一样使用BigDecimal进行精确的浮点数计算?

背景

最近在写go,遇到一个问题,go没有自带的decimal类型,只有一个math/big用于大数运算。遇到购物支付的场景,这时候使用浮点数计算非常有必要,而且通常都是精确到小数点后两位。

查阅了一些资料,找到了一个star数比较高的解决方案。

shopspring/decimal: https://github.com/shopspring/decimal
文档地址: https://pkg.go.dev/github.com/shopspring/decimal

需要注意的是: Decimal库“只能”表示小数点后最多 2^31 位的数字。但是这已经足够满足我们目前的需求。

它的原理是使用十进制定点数表示法,有多少位小数就小数点后移多少位,value保存移之后的整数,exp保存小数点后的数位个数,number=value*10^exp,因为移小数点后的整数可能很大,所以这里借用标准包里的math/big表示这个大整数。exp使用了int32,这也就是为什么Decimal库最多“只能”表示小数点后最多 2^31 位的数字的原因。

以下是源码中Decimal的定义

type Decimal struct {
	value *big.Int

	// NOTE(vadim): this must be an int32, because we cast it to float64 during
	// calculations. If exp is 64 bit, we might lose precision.
	// If we cared about being able to represent every possible decimal, we
	// could make exp a *big.Int but it would hurt performance and numbers
	// like that are unrealistic.
	exp int32
}

示例

使用前提: Go version >=1.7

go get github.com/shopspring/decimal
package main

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

func main() {
	// 将string类型转换为decimal类型
	price, err := decimal.NewFromString("136.02")
	if err != nil {
		panic(err)
	}
	
	// 支持负数
	n, err := decimal.NewFromString("-123.4567")
	n.String() // output: "-123.4567"

	// 将int类型转为decimal类型
	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

	// 支持科学计数法
	fmt.Println(NewFromFloat(-1e13).String()) // output: "-10000000000000"
	
	// 除,默认是没有小数点后的精确位数
	d1 := decimal.NewFromFloat(2).Div(decimal.NewFromFloat(3))
	d1.String() // output: "0.6666666666666667"

	// 小数点后保留三位
	decimal.DivisionPrecision = 3
	d4 := decimal.NewFromFloat(2).Div(decimal.NewFromFloat(3))
	d4.String() // output: "0.667"
}

你可能感兴趣的:(Go)