ITADN
shopspring/decimal
README.md
以下内容由 AI 翻译,如有问题请点此提交 issue 反馈

十进制

ci GoDoc Go Report Card

Go 中的任意精度定点十进制数。

注意: Decimal 库“仅”能表示小数点后最多 2^31 位的数字。

特性

  • 零值为 0,且无需初始化即可安全使用
  • 加法、减法、乘法,无精度损失
  • 指定精度的除法
  • 数据库/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 - 任意精度,可变且 API 丰富,类似于 big.Int,性能优于本库
  • alpacahq/alpacadecimal - 高性能,低精度(12 位),与本库完全兼容的 API
  • govalues/decimal - 高性能,零分配,低精度(19 位)
  • 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)。那个人将收到一分钱的零头。

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

为什么 API 与 big.Int 的相似?

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

相比之下,使用十进制数很难犯这类错误。十进制数 的行为与其他 go 数字类型类似:尽管 a = b 不会将 b 深拷贝到 a,但修改 Decimal 是不可能的,因为所有 Decimal 方法 都返回新的 Decimal,而不修改原始值。缺点是 这会导致额外的内存分配,因此 Decimal 的性能较低。 我的假设 是,如果你在使用 Decimal,你可能更关心正确性 而非性能。

License

MIT 许可证 (MIT)

这是 fpd.Decimal 的一个经过大量修改的分支,该分支同样以 MIT 许可证发布。