golang高精度定点十进制数值计算插件库decimal的使用

Golang高精度定点十进制数值计算插件库decimal的使用

decimal是一个用于Go语言的任意精度定点十进制数值计算库,特别适合处理货币计算等需要精确十进制表示的场景。

特性

  • 零值默认为0,无需初始化即可安全使用
  • 加法、减法、乘法运算不会丢失精度
  • 可指定精度的除法运算
  • 支持database/sql序列化/反序列化
  • 支持JSON和XML序列化/反序列化

安装

go get github.com/shopspring/decimal

要求

Go版本 >=1.10

使用示例

下面是一个完整的使用示例,展示了decimal库的基本操作:

package main

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

func main() {
	// 从字符串创建Decimal
	price, err := decimal.NewFromString("136.02")
	if err != nil {
		panic(err)
	}

	// 从整数创建Decimal
	quantity := decimal.NewFromInt(3)

	// 从字符串创建Decimal(忽略错误)
	fee, _ := decimal.NewFromString(".035")
	taxRate, _ := decimal.NewFromString(".08875")

	// 计算小计(价格×数量)
	subtotal := price.Mul(quantity)

	// 计算税前金额(小计×(1+费率))
	preTax := subtotal.Mul(fee.Add(decimal.NewFromFloat(1)))

	// 计算总金额(税前金额×(1+税率))
	total := preTax.Mul(taxRate.Add(decimal.NewFromFloat(1)))

	// 输出结果
	fmt.Println("Subtotal:", subtotal)                      // 小计: 408.06
	fmt.Println("Pre-tax:", preTax)                         // 税前: 422.3421
	fmt.Println("Taxes:", total.Sub(preTax))                // 税额: 37.482861375
	fmt.Println("Total:", total)                            // 总计: 459.824961375
	fmt.Println("Tax rate:", total.Sub(preTax).Div(preTax)) // 税率: 0.08875
}

为什么不用float64?

float64(或任何二进制浮点类型)无法精确表示像0.1这样的数字。例如:

var n float64 = 0
for i := 0; i < 100; i++ {
    n += 0.1
}
fmt.Println(n) // 输出9.999999999999831而不是10

随着时间的推移,这些小误差会累积起来,导致严重的问题。

为什么不用big.Rat?

big.Rat适合表示有理数,但decimal更适合表示货币。例如:

x := decimal.NewFromFloat(1).Div(decimal.NewFromFloat(3)) // 0.333...
y := decimal.NewFromFloat(1).Div(decimal.NewFromFloat(3)) // 0.333...
z := decimal.NewFromFloat(1).Sub(x).Sub(y)                // 0.334

decimal打印的字符串精确表示数字,不会出现big.Rat中常见的精度丢失问题。

性能考虑

decimal的API设计更注重正确性而非性能。所有方法都返回新的Decimal而不修改原始值,这会导致额外的内存分配。如果需要更高性能的解决方案,可以考虑其他替代库。

替代库

  • cockroachdb/apd - 任意精度,可变,API类似big.Int,性能更好
  • alpacahq/alpacadecimal - 高性能,低精度(12位),完全兼容API
  • govalues/decimal - 高性能,零分配,低精度(19位)
  • greatcloak/decimal - 专注于账单和电子商务用例,包含BSON编组支持

decimal库是处理货币和需要精确十进制计算的理想选择,它提供了简单直观的API,同时保证了计算的精确性。


更多关于golang高精度定点十进制数值计算插件库decimal的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang高精度定点十进制数值计算插件库decimal的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Golang高精度定点十进制数值计算库 - decimal

decimal是Go语言中一个优秀的高精度定点十进制数值计算库,特别适合金融、货币等需要精确小数计算的场景。下面我将详细介绍decimal库的使用方法。

安装decimal库

go get github.com/shopspring/decimal

基本使用

创建Decimal值

package main

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

func main() {
	// 从字符串创建
	a := decimal.NewFromString("123.456")
	b := decimal.NewFromString("78.90")

	// 从float创建(不推荐,可能有精度问题)
	c := decimal.NewFromFloat(123.456)

	// 从整数创建
	d := decimal.NewFromInt(100)

	fmt.Println(a, b, c, d)
}

基本运算

func basicOperations() {
	a := decimal.NewFromString("123.456")
	b := decimal.NewFromString("78.90")

	// 加法
	sum := a.Add(b)
	fmt.Println("加法:", sum) // 202.356

	// 减法
	diff := a.Sub(b)
	fmt.Println("减法:", diff) // 44.556

	// 乘法
	product := a.Mul(b)
	fmt.Println("乘法:", product) // 9740.6784

	// 除法
	quotient := a.Div(b)
	fmt.Println("除法:", quotient) // 1.564714828897341698

	// 取余
	remainder := decimal.NewFromInt(10).Mod(decimal.NewFromInt(3))
	fmt.Println("取余:", remainder) // 1
}

比较运算

func compareOperations() {
	a := decimal.NewFromString("123.456")
	b := decimal.NewFromString("78.90")
	c := decimal.NewFromString("123.456")

	fmt.Println("a > b:", a.GreaterThan(b)) // true
	fmt.Println("a < b:", a.LessThan(b))    // false
	fmt.Println("a == c:", a.Equal(c))      // true
	fmt.Println("a != b:", a.Equal(b))      // false
}

四舍五入和截断

func roundingOperations() {
	num := decimal.NewFromString("123.456789")

	// 四舍五入到小数点后2位
	rounded := num.Round(2)
	fmt.Println("四舍五入2位:", rounded) // 123.46

	// 向下取整
	floor := num.RoundFloor(2)
	fmt.Println("向下取整2位:", floor) // 123.45

	// 向上取整
	ceil := num.RoundCeil(2)
	fmt.Println("向上取整2位:", ceil) // 123.46

	// 截断
	truncated := num.Truncate(2)
	fmt.Println("截断2位:", truncated) // 123.45
}

高级运算

func advancedOperations() {
	// 平方根
	sqrt, _ := decimal.NewFromString("16").Sqrt()
	fmt.Println("平方根:", sqrt) // 4

	// 幂运算
	power := decimal.NewFromInt(2).Pow(decimal.NewFromInt(3))
	fmt.Println("幂运算:", power) // 8

	// 绝对值
	abs := decimal.NewFromString("-123.456").Abs()
	fmt.Println("绝对值:", abs) // 123.456
}

序列化和反序列化

func serialization() {
	// 转换为字符串
	num := decimal.NewFromString("123.456")
	str := num.String()
	fmt.Println("字符串表示:", str)

	// 转换为float64(可能丢失精度)
	f, _ := num.Float64()
	fmt.Println("float64:", f)

	// JSON序列化
	type Invoice struct {
		Amount decimal.Decimal `json:"amount"`
	}
	inv := Invoice{Amount: num}
	jsonData, _ := json.Marshal(inv)
	fmt.Println("JSON:", string(jsonData))
}

实际应用示例

金融计算示例

func financialExample() {
	// 计算复利
	principal := decimal.NewFromInt(1000)      // 本金
	rate := decimal.NewFromFloat(0.05)         // 年利率5%
	years := 5                                 // 5年

	// 复利公式: A = P(1 + r)^n
	factor := decimal.NewFromInt(1).Add(rate)  // (1 + r)
	factor = factor.Pow(decimal.NewFromInt(int64(years))) // (1 + r)^n
	amount := principal.Mul(factor)           // P * (1 + r)^n

	fmt.Println("5年后金额:", amount.Round(2)) // 1276.28
}

购物车计算示例

func shoppingCartExample() {
	// 商品列表
	items := []struct {
		name     string
		price    decimal.Decimal
		quantity int64
	}{
		{"笔记本电脑", decimal.NewFromString("5999.99"), 1},
		{"鼠标", decimal.NewFromString("129.50"), 2},
		{"键盘", decimal.NewFromString("299.00"), 1},
	}

	// 计算小计
	var subtotal decimal.Decimal
	for _, item := range items {
		subtotal = subtotal.Add(item.price.Mul(decimal.NewFromInt(item.quantity)))
	}

	// 计算税费(8%)
	taxRate := decimal.NewFromFloat(0.08)
	tax := subtotal.Mul(taxRate).Round(2)

	// 计算总价
	total := subtotal.Add(tax)

	fmt.Println("小计:", subtotal.Round(2))
	fmt.Println("税费:", tax)
	fmt.Println("总计:", total.Round(2))
}

注意事项

  1. 避免从float创建NewFromFloat可能会有精度问题,推荐使用NewFromString
  2. 除法精度:除法运算默认保留16位小数,可以使用DivRound指定精度
  3. 并发安全:Decimal类型是不可变的,因此是并发安全的
  4. 性能考虑:Decimal运算比原生float运算慢,在非必要场景不要滥用

decimal库提供了丰富的API,可以满足绝大多数精确计算的需求,特别适合金融、电商等需要精确小数计算的场景。

回到顶部