1. 简介
Go Decimal库是一个在Go语言中处理任意精度定点小数的强大工具。它允许进行加法、减法、乘法和除法运算,而不会丢失精度。此外,它还提供了诸如数据库/SQL序列化/反序列化以及JSON/XML序列化/反序列化等功能。
2. 安装
要安装Go Decimal库,可以使用以下命令:
go get github.com/shopspring/decimal
请注意,Decimal库要求Go版本 >=1.7。
3. 基本用法
要在Go程序中使用Decimal库,导入"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
}
4. 创建Decimal变量
Decimal库提供了多种方法来创建Decimal变量。以下是支持的API:
-
decimal.NewFromBigInt(value *big.Int, exp int32) Decimal
-
decimal.NewFromFloat(value float64) Decimal
-
decimal.NewFromFloat32(value float32) Decimal
-
decimal.NewFromFloatWithExponent(value float64, exp int32) Decimal
-
decimal.NewFromFormattedString(value string, replRegexp *regexp.Regexp) (Decimal, error)
-
decimal.NewFromInt(value int64) Decimal
-
decimal.NewFromInt32(value int32) Decimal
-
decimal.NewFromString(value string) (Decimal, error)
-
decimal.RequireFromString(value string) Decimal
5. 算术操作
Go Decimal库提供了几种可以对Decimal变量进行的算术操作。以下是一些支持的操作:
-
Add(d2 Decimal) Decimal
:将两个Decimal值相加并返回结果。 -
Sub(d2 Decimal) Decimal
:从一个Decimal值中减去另一个Decimal值并返回结果。 -
Div(d2 Decimal) Decimal
:将一个Decimal值除以另一个Decimal值并返回结果。 -
DivRound(d2 Decimal, precision int32) Decimal
:将一个Decimal值除以另一个Decimal值,并指定精度返回结果。 -
Mod(d2 Decimal) Decimal
:计算一个Decimal值除以另一个Decimal值的模(余数)并返回结果。 -
Mul(d2 Decimal) Decimal
:将两个Decimal值相乘并返回结果。
您可以使用这些操作对Decimal值进行常见的算术运算。以下是一个演示使用这些操作的示例:
price, _ := decimal.NewFromString("136.02")
quantity := decimal.NewFromInt(3)
subtotal := price.Mul(quantity)
tax := subtotal.Mul(decimal.NewFromFloat(0.08875))
total := subtotal.Add(tax)
fmt.Println("Subtotal:", subtotal) // Subtotal: 408.06
fmt.Println("Tax:", tax) // Tax: 36.244985
fmt.Println("Total:", total) // Total: 444.304985
在上面的示例中,我们通过使用Mul()
方法将price
和quantity
相乘来计算subtotal
。然后我们通过将subtotal
和税率相乘来计算tax
。最后,我们通过使用Add()
方法将subtotal
和tax
相加来计算total
。
6. 四舍五入操作
Go Decimal库提供了几种四舍五入操作,可以用于将Decimal值舍入到指定的精度。以下是一些可用的四舍五入操作:
-
Round(places int32) Decimal
:将小数舍入到指定的小数位数。 -
RoundBank(places int32) Decimal
:使用银行家舍入法将小数舍入到指定的小数位数。 -
RoundCash(interval uint8) Decimal
:将小数舍入到特定的间隔,如5分、10分、25分、50分或1元。 -
RoundCeil(places int32) Decimal
:向正无穷舍入小数。 -
RoundDown(places int32) Decimal
:向零方向舍入小数。 -
RoundFloor(places int32) Decimal
:向负无穷舍入小数。 -
RoundUp(places int32) Decimal
:朝离零较远的方向舍入小数。
6.1. Round
Round将小数舍入到places位小数。如果places < 0,则会将整数部分舍入到最近的10^(-places)。
NewFromFloat(5.45).Round(1).String() // 输出:"5.5"
NewFromFloat(545).Round(-1).String() // 输出:"550"
6.2. RoundBank
RoundBank将小数舍入到places位小数。如果要舍入的最后一位小数距离最近的两个整数的距离相等,则舍入值取偶数。
如果places < 0,则会将整数部分舍入到最近的10^(-places)。
NewFromFloat(5.45).RoundBank(1).String() // 输出:"5.4"
NewFromFloat(545).RoundBank(-1).String() // 输出:"540"
NewFromFloat(5.46).RoundBank(1).String() // 输出:"5.5"
NewFromFloat(546).RoundBank(-1).String() // 输出:"550"
NewFromFloat(5.55).RoundBank(1).String() // 输出:"5.6"
NewFromFloat(555).RoundBank(-1).String() // 输出:"560"
6.3. RoundCash
RoundCash(又称为现金/分/爱尔兰枚的舍入)将小数舍入到特定的间隔上。现金交易的应付金额将舍入为最接近的最小货币单位的倍数。可用的间隔有:5、10、25、50和100;其他任何数字都会抛出异常。
5: 5分舍入 3.43 => 3.45
10: 10分舍入 3.45 => 3.50(5被舍入上去)
25: 25分舍入 3.41 => 3.50
50: 50分舍入 3.75 => 4.00
100: 100分舍入 3.50 => 4.00
6.4. RoundCeil
RoundCeil向正无穷方向舍入小数。
NewFromFloat(545).RoundCeil(-2).String() // 输出:"600"
NewFromFloat(500).RoundCeil(-2).String() // 输出:"500"
NewFromFloat(1.1001).RoundCeil(2).String() // 输出:"1.11"
NewFromFloat(-1.454).RoundCeil(1).String() // 输出:"-1.5"
6.5. RoundDown
RoundDown向零方向舍入小数。
NewFromFloat(545).RoundDown(-2).String() // 输出:"500"
NewFromFloat(-500).RoundDown(-2).String() // 输出:"-500"
NewFromFloat(1.1001).RoundDown(2).String() // 输出:"1.1"
NewFromFloat(-1.454).RoundDown(1).String() // 输出:"-1.5"
6.6. RoundFloor
RoundFloor向负无穷方向舍入小数。
NewFromFloat(545).RoundFloor(-2).String() // 输出:"500"
NewFromFloat(-500).RoundFloor(-2).String() // 输出:"-500"
NewFromFloat(1.1001).RoundFloor(2).String() // 输出:"1.1"
NewFromFloat(-1.454).RoundFloor(1).String() // 输出:"-1.4"
6.7. 向上取整
RoundUp 向远离零的方向舍入十进制数。
NewFromFloat(545).RoundUp(-2).String() // 输出: "600"
NewFromFloat(500).RoundUp(-2).String() // 输出: "500"
NewFromFloat(1.1001).RoundUp(2).String() // 输出: "1.11"
NewFromFloat(-1.454).RoundUp(1).String() // 输出: "-1.4"
7. Decimal类型转换为字符串
Go Decimal 库提供了将Decimal数值转换为字符串表示的方法,这里列举了一些可用的方法:
-
String(): string
:返回带有固定小数点的十进制数的字符串表示。 -
StringFixed(places int32) string
:返回指定小数位数的四舍五入的固定小数点字符串表示。 -
StringFixedBank(places int32) string
:返回指定小数位数的四舍五入(银行家舍入)的固定小数点字符串表示。
您可以根据需求选择相应的方法。下面是一个将十进制数转换为字符串的示例:
d := decimal.NewFromFloat(5.45)
str := d.String()
fmt.Println("十进制数的字符串表示:", str) // 十进制数的字符串表示: 5.45
// StringFixed 示例
NewFromFloat(0).StringFixed(2) // 输出: "0.00"
NewFromFloat(0).StringFixed(0) // 输出: "0"
NewFromFloat(5.45).StringFixed(0) // 输出: "5"
NewFromFloat(5.45).StringFixed(1) // 输出: "5.5"
NewFromFloat(5.45).StringFixed(2) // 输出: "5.45"
NewFromFloat(5.45).StringFixed(3) // 输出: "5.450"
NewFromFloat(545).StringFixed(-1) // 输出: "550"
// StringFixedBank 示例
NewFromFloat(0).StringFixedBank(2) // 输出: "0.00"
NewFromFloat(0).StringFixedBank(0) // 输出: "0"
NewFromFloat(5.45).StringFixedBank(0) // 输出: "5"
NewFromFloat(5.45).StringFixedBank(1) // 输出: "5.4"
NewFromFloat(5.45).StringFixedBank(2) // 输出: "5.45"
NewFromFloat(5.45).StringFixedBank(3) // 输出: "5.450"
NewFromFloat(545).StringFixedBank(-1) // 输出: "540"
8. 常见问题
问: 为什么不直接使用 float64? 答: float64 无法准确地表示像 0.1 这样的数字。它可能导致小的误差,在处理财务计算等涉及金额的情况下,这些误差可能会随着时间的推移累积并导致重大问题。
问: 为什么不直接使用 big.Rat? 答: 虽然 big.Rat 可以表示有理数,但对于表示货币来说并不适用。Decimal数在财务计算中更好,因为它可以准确地表示小数分数而不会丢失精度。
问: 为什么 API 不和 big.Int 的类似? 答: Decimal 库的 API 更注重易用性和正确性而非性能。虽然 big.Int 的 API 为了性能原因减少了内存分配,但这可能导致复杂且容易出错的代码。Decimal 库的 API 被设计为简单易懂。