How to represent currency in Go?

huangapple go评论78阅读模式
英文:

How to represent currency in Go?

问题

在Go语言中,正确存储和进行货币运算的方式是使用decimal包。Go语言本身没有内置的十进制类型,但是可以使用第三方库来处理十进制数值。decimal包提供了高精度的十进制数值操作,适用于货币计算等需要精确结果的场景。使用decimal包可以避免使用浮点数进行货币运算时可能出现的精度问题。

英文:

What is the correct way to store and do arithmetic on currency in Go? There doesn't seem to be a corresponding decimal type and using floats is a big no.

答案1

得分: 28

我会为你翻译以下内容:

我认为一种方法是使用适当大小的整数类型来存储金额,并将其标准化为最低可能的金额。例如,如果你需要以美元为单位存储金额,精确到一分钱,可以将数值乘以100,然后以完整的分为单位进行存储。

另一种方法是实现一个自定义类型,该类型模拟了其他一些语言中的“十进制”类型,即使用两个整数来表示金额。

英文:

I'd say a way to go is to store amounts of money using properly sized integer type, normalized to the lowest possible amount. Say, if you need to store amounts in US dollars down to one cent, multiply your values by 100 and hence store them in full cents.

Another way is to implement a custom type which would model what is "decimal" in some other languages, that is, it would use two integer numbers to represent amount of money.

答案2

得分: 15

这似乎是一个很好的机会来创建一个类型,以安全而精确的整数方式存储值,但同时提供了你希望从十进制类型中获得的额外行为。例如,一个快速的实现可能如下所示:

// USD 表示以美分为单位的美元金额
type USD int64

// ToUSD 将 float64 转换为 USD
// 例如,1.23 转换为 $1.23,1.345 转换为 $1.35
func ToUSD(f float64) USD {
    return USD((f * 100) + 0.5)
}

// Float64 将 USD 转换为 float64
func (m USD) Float64() float64 {
    x := float64(m)
    x = x / 100
    return x
}

// Multiply 安全地将 USD 值乘以 float64,四舍五入到最近的美分
func (m USD) Multiply(f float64) USD {
    x := (float64(m) * f) + 0.5
    return USD(x)
}

// String 返回格式化的 USD 值
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)

输出结果为:

Product costs $9.09. Tax is 9.75%.

Floats: 9.089999999999999858 * 0.097500000000000003 = 0.886275000000000035

USD:    $9.09 * 0.0975 = $0.89
英文:

This seems like a great opportunity to create a type, which stores the value in a safe and precise integer-based way, but gives you extra behavior you'd want from a decimal type. For instance, a quick implementation might look like this (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)
}

The given type behaves the way one might expect, especially given tricky use-cases.

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)

> Product costs $9.09. Tax is 9.75%.

> Floats: 9.089999999999999858 * 0.097500000000000003 = 0.886275000000000035

> USD: $9.09 * 0.0975 = $0.89

答案3

得分: 9

有理数是表示货币值的很好的解决方案。也就是说,有一个分子和一个分母的类型。

通常,货币数据结构过于复杂 - 例如Java的BigDecimal。一个更加数学一致的方法是定义一个处理有理数的类型。当使用64位整数时,可以准确高效地表示大范围的数字。与需要将二进制分数转换为十进制分数的任何解决方案相比,错误和舍入问题不是那么严重。

编辑:Go标准库包括任意精度整数和有理数。Rat类型在货币方面效果很好,特别是对于那些需要任意精度的情况,例如外汇。这里有一个示例

编辑2:我广泛使用了decimal.Decimal Shopspring包。在内部,它将big.Int与指数结合起来,提供了一个具有几乎无限范围的定点小数。Decimal类型是一个有理数,其中分母始终是十的幂,这在实践中非常有效。

英文:

Rational numbers are quite a good solution for representing money values. That is, a type that has a numerator and a denominator.

Often monetary data structures are overly complex - Java's BigDecimal being an example. A more mathematically-consistent approach is to define a type that handles rational numbers. When 64bit integers are used, a huge range of numbers can be accurately and efficiently represented. Errors and rounding issues are less of a problem than for any solution that needs to convert binary fractions to/from decimal fractions.

Edit: The Go standard library includes arbitrary-precision integers and rational numbers. The Rat type will work well for currency, especially for those cases that require arbitrary precision, e.g. foreign exchange. Here's an example.

Edit 2: I have used the decimal.Decimal Shopspring package extensively. Under the hood, this combines big.Int with an exponent to provide a fixed-point decimal with a nearly-unlimited range of values. The Decimal type is a rational number where the denominator is always a power of ten, which works very well in practice.

答案4

得分: 5

实际上有几个包实现了十进制类型,尽管它们之间没有明确的领导者。

英文:

There are actually a few packages implementing a decimal type, though there's no clear leader among them.

答案5

得分: 0

对于我来说,最好的选择是将数据存储为表示欧元的整数,以表示欧元的分,并使用以下之一来显示:

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
--- PASS: TestCentsToEuros (0.00s)
PASS

希望这能帮到你!

英文:

for me the best options is store data as int representing cents of EUR and then use one of this to show:

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

--- PASS: TestCentsToEuros (0.00s)
PASS

Process finished with the exit code 0

huangapple
  • 本文由 发表于 2013年12月15日 23:54:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/20596428.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定