在 Go 中存储和计算货币的正确方法是什么?似乎没有相应的小数类型,使用浮点数是一个很大的问题。
我想说的一种方法是使用适当大小的整数类型存储金额,并标准化为尽可能低的金额。比方说,如果您需要将美元金额存储到一美分,请将您的值乘以 100,然后以整美分存储它们。
另一种方法是实现一个自定义类型,它将模拟其他一些语言中的“十进制”,也就是说,它将使用两个整数来表示金额。
这似乎是创建类型的绝佳机会,它以安全且精确的基于整数的方式存储值,但为您提供了小数类型所需的额外行为。例如,快速实现可能如下所示 (https://play.golang.org/p/nYbLiadQOc):
// USD represents US dollar amount in terms of cents
type USD int64
// ToUSD converts a float64 to USD
// e.g. 1.23 to $1.23, 1.345 to $1.35
func ToUSD(f float64) USD {
return USD((f * 100) + 0.5)
}
// Float64 converts a USD to float64
func (m USD) Float64() float64 {
x := float64(m)
x = x / 100
return x
}
// Multiply safely multiplies a USD value by a float64, rounding
// to the nearest cent.
func (m USD) Multiply(f float64) USD {
x := (float64(m) * f) + 0.5
return USD(x)
}
// String returns a formatted USD value
func (m USD) String() string {
x := float64(m)
x = x / 100
return fmt.Sprintf("$%.2f", x)
}
给定类型的行为符合人们的预期,尤其是在棘手的用例中。
fmt.Println("Product costs $9.09. Tax is 9.75%.")
f := 9.09
t := 0.0975
ft := f * t
fmt.Printf("Floats: %.18f * %.18f = %.18f\n", f, t, ft)
u := ToUSD(9.09)
ut := u.Multiply(t)
fmt.Printf("USD: %v * %v = %v\n", u, t, ut)
产品售价 9.09 美元。税率为 9.75%。
浮点数:9.089999999999999858 * 0.097500000000000003 = 0.886275000000000035
美元:9.09 美元 * 0.0975 = 0.89 美元
有理数是表示货币价值的很好的解决方案。也就是说,具有分子和分母的类型。
货币数据结构通常过于复杂——Java 的 BigDecimal 就是一个例子。一种在数学上更一致的方法是定义一个处理有理数的类型。当使用 64 位整数时,可以准确有效地表示很大范围的数字。与任何需要将二进制分数转换为十进制分数或从十进制分数转换的解决方案相比,错误和舍入问题不是什么大问题。
编辑:Go 标准库包括 任意精度整数和有理数。 Rat 类型适用于货币,特别是对于那些需要任意精度的情况,例如外汇。这是一个例子.
编辑 2:我广泛使用了
decimal.Decimal
Shopspring 包。在引擎盖下,这将 big.Int
与指数相结合,以提供具有几乎无限范围的值的定点小数。 Decimal
类型是分母始终为 10 的幂的有理数,在实践中非常有效。
实际上有几个包实现了小数类型,尽管它们之间没有明确的领导者。
对我来说,最好的选择是将数据存储为代表欧元分的整数,然后使用其中一个来显示:
func CentsToEuros(cents int) string {
euros := float64(cents) / 100.0
return fmt.Sprintf("%.2f", euros)
}
func CentsToEurosItalianView(cents int) string {
it := language.Italian
printer := message.NewPrinter(it)
euros := float64(cents) / 100.0
return printer.Sprintf("%.2f", euros)
}
func CentsToEurosItalianViewWithCurrency(cents int) string {
eur := currency.EUR
it := language.Italian
printer := message.NewPrinter(it)
euros := float64(cents) / 100.0
amount := eur.Amount(euros)
return printer.Sprint(amount)
}
concurrencyUtils_test.go:7: ###################### CentsToEuros ######################
concurrencyUtils_test.go:11: value 1 result 0.01
concurrencyUtils_test.go:11: value 2 result 0.02
concurrencyUtils_test.go:11: value 3 result 0.03
concurrencyUtils_test.go:11: value 50 result 0.50
concurrencyUtils_test.go:11: value 500 result 5.00
concurrencyUtils_test.go:11: value 5000 result 50.00
concurrencyUtils_test.go:11: value 100200050 result 1002000.50
concurrencyUtils_test.go:11: value 5 result 0.05
concurrencyUtils_test.go:14: ###################### CentsToEurosItalianViews ######################
concurrencyUtils_test.go:17: value 1 result 0,01
concurrencyUtils_test.go:17: value 2 result 0,02
concurrencyUtils_test.go:17: value 3 result 0,03
concurrencyUtils_test.go:17: value 50 result 0,50
concurrencyUtils_test.go:17: value 500 result 5,00
concurrencyUtils_test.go:17: value 5000 result 50,00
concurrencyUtils_test.go:17: value 100200050 result 1.002.000,50
concurrencyUtils_test.go:17: value 5 result 0,05
concurrencyUtils_test.go:20: ###################### CentsToEurosItalianViews ######################
concurrencyUtils_test.go:23: value 1 result EUR 0,01
concurrencyUtils_test.go:23: value 2 result EUR 0,02
concurrencyUtils_test.go:23: value 3 result EUR 0,03
concurrencyUtils_test.go:23: value 50 result EUR 0,50
concurrencyUtils_test.go:23: value 500 result EUR 5,00
concurrencyUtils_test.go:23: value 5000 result EUR 50,00
concurrencyUtils_test.go:23: value 100200050 result EUR 1.002.000,50
concurrencyUtils_test.go:23: value 5 result EUR 0,05
--- 通过:TestCentsToEuros (0.00s) 通过
进程结束,退出代码为 0