shopspring/decimal
GitHub: shopspring/decimal
Go 语言的任意精度定点十进制数库,旨在解决浮点数精度丢失问题,特别适合财务与货币计算。
Stars: 7450 | Forks: 673
# 十进制
[](https://github.com/shopspring/decimal/actions/workflows/ci.yml)
[](https://godoc.org/github.com/shopspring/decimal)
[](https://goreportcard.com/report/github.com/shopspring/decimal)
go 语言中的任意精度定点十进制数。
_注意:_ decimal 库“只能”表示小数点后最多 2^31 位的数字。
## 特性
* 零值为 0,无需初始化即可安全使用
* 加法、减法、乘法无精度损失
* 支持指定精度的除法
* 支持 database/sql 序列化/反序列化
* 支持 JSON 和 XML 序列化/反序列化
## 安装
运行 `go get github.com/shopspring/decimal`
## 环境要求
decimal 库要求 Go 版本 `>=1.10`
## 文档
http://godoc.org/github.com/shopspring/decimal
## 用法
```
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
}
```
## 备选库
在处理十进制数时,您可能会遇到本库并不完美适用的问题。
幸运的是,感谢我们出色的社区,还有许多其他的库供您选择。
探索其他备选方案,找到最适合您需求的吧 :)
* [cockroachdb/apd](https://github.com/cockroachdb/apd) - 任意精度,可变且具有类似于 `big.Int` 的丰富 API,性能比本库更高
* [alpacahq/alpacadecimal](https://github.com/alpacahq/alpacadecimal) - 高性能,低精度(12 位),与本库完全兼容的 API
* [govalues/decimal](https://github.com/govalues/decimal) - 高性能,零内存分配,低精度(19 位)
* [greatcloak/decimal](https://github.com/greatcloak/decimal) - 专注于账单和电子商务 Web 应用相关用例的分支,包含开箱即用的 BSON 序列化支持
## 常见问题
#### 为什么不直接使用 float64?
因为 float64(实际上,任何二进制浮点数类型)都无法精确表示像 `0.1` 这样的数字。
考虑一下这段代码:http://play.golang.org/p/TQBd4yJe6B 您可能期望它输出 `10`,但实际上它输出的是 `9.999999999999831`。随着时间的推移,这些微小的误差会不断累加!
#### 为什么不直接使用 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/lCZZs0w9KE
使用 Decimal 时,打印出来的字符串精确地表示了该数字。因此,如果您有 `x = y = 1/3`(精度为 3),它们实际上将等于 0.333,并且当您执行 `z = 1 - x - y` 时,`z` 将等于 .334。没有任何资金会去向不明!
您仍然需要小心。如果您想将一个数字 `N` 分成 3 份,您不能直接将 `N/3` 发送给三个不同的人。您必须选择一个人发送 `N - (2/3*N)`。那个人将收到作为余数的一分钱的一部分。
但是,使用 Decimal 比使用 big.Rat 更容易做到谨慎操作。
#### 为什么 API 不类似于 big.Int 的 API?
big.Int 的 API 旨在减少内存分配次数以实现最大性能。这对于其用例来说是有意义的,但代价是 API 显得笨拙且容易误用。
例如,要将两个 big.Int 相加,您需要这样做:`z := new(big.Int).Add(x, y)`。不熟悉此 API 的开发者可能会尝试执行 `z := a.Add(a, b)`。这会修改 `a` 并将 `z` 设置为 `a` 的别名,这可能出乎他们的意料。它还会修改 `a` 的任何其他别名。
这是一个使用 big.Int 的 API 可能引入的微妙 bug 的例子:
https://play.golang.org/p/x2R_78pa8r
相比之下,使用 decimal 很难犯这样的错误。Decimal 的行为与其他 go 数字类型一样:即使 `a = b` 不会将 `b` 深拷贝到 `a` 中,修改一个 Decimal 也是不可能的,因为所有的 Decimal 方法都返回新的 Decimal,而不会修改原始值。其缺点是这会导致额外的内存分配,因此 Decimal 的性能较低。我的假设是,如果您使用 Decimal,您可能更关心正确性而不是性能。
## 许可证
The MIT License (MIT)
这是 [fpd.Decimal](https://github.com/oguzbilgic/fpd) 的一个深度修改的分支,它同样是在 MIT 许可证下发布的。
标签:EVTX分析, Go, Homebrew安装, Ruby工具, 定点小数, 数学库, 日志审计, 高精度计算