Go – 货币计算

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

Go - monetary calculations

问题

我正在尝试使用Go进行货币计算。

在标准库中查找时,big/math 似乎是我可以使用的一个库。
我运行了一些代码来检查它的工作方式。

首先,我使用了 Float.SetFloat64() 方法。

var f = 18.9
var f2 = 1.65
bf := new(big.Float)
bf.Add(bf, new(big.Float).SetFloat64(f))
bf.Add(bf, new(big.Float).SetFloat64(f2))
result, _ := bf.Float64()
fmt.Println(result)

这给出了结果:20.549999999999997,就像使用 float64 类型进行计算一样。
由于我处理的是货币,结果应该是 20.55

相反,当使用 Float.SetString() 方法时:

var s = fmt.Sprintf("%f", 18.9)
var s2 = fmt.Sprintf("%f", 1.65)
bf := new(big.Float)
bf2, _ := new(big.Float).SetString(s)
bf3, _ := new(big.Float).SetString(s2)
bf.Add(bf, bf2)
bf.Add(bf, bf3)
result, _ := bf.Float64()
fmt.Println(result)

这给出了结果:20.55
看起来我可以使用这种方法来进行我的目的(但我不确定)。

我的问题是:

  1. 使用 Float.SetFloat64()Float.SetString() 有什么区别?

  2. 在进行货币计算时,使用 Float.SetString() 有什么注意事项吗?

提前感谢您的回答,谅解我的英语。

【编辑】

我知道应该避免使用浮点类型来表示货币值。
但是选择类型并不完全在我的控制之下。

我想知道上述两个问题的答案(或其中之一)。

或者,为什么 Float.SetString() 给我看似正确的结果的原因也会很有帮助。

英文:

I am trying to do monetary calculations with Go.

Looking around the standard library, big/math seems to be the one I can make use of.
And I ran some codes to check how it works.

First I used Float.SetFloat64()

var f = 18.9
var f2 = 1.65
bf := new(big.Float)
bf.Add(bf, new(big.Float).SetFloat64(f))
bf.Add(bf, new(big.Float).SetFloat64(f2))
result, _ := bf.Float64()
fmt.Println(result)

This gives me the result: 20.549999999999997
Just like the calculation with float64 type.
Since I'm dealing with money, the result must be 20.55.

Instead, when using Float.SetString()

var s = fmt.Sprintf("%f", 18.9)
var s2 = fmt.Sprintf("%f", 1.65)
bf := new(big.Float)
bf2, _ := new(big.Float).SetString(s)
bf3, _ := new(big.Float).SetString(s2)
bf.Add(bf, bf2)
bf.Add(bf, bf3)
result, _ := bf.Float64()
fmt.Println(result)

This gives me the result: 20.55
It SEEMs I can use this for my purpose (but I am not sure..).

My questions are

  1. Why the difference beteween using Float.SetFloat64() and Float.SetString()?

  2. Are there any pitfalls when using Float.SetString() for monetary calculations?

Thanks in advance, and forgive my English.

[EDIT]

I know float types should be avoided for representing money value.
but the choice of the type is not really under my control..

I want to know the answers to either(or both) of the above two questions.

Or the reason why Float.SetString() gives me the seemingly correct results is also helpful.

答案1

得分: 11

永远不要使用浮点数来表示货币。它是一种不精确的类型,所以你最终不得不四舍五入计算结果,这样就会损失(或者增加)一分钱。你可能认为这没关系,但你的会计师会告诉你这是不行的。你必须保持完全准确。

所以要么使用专门的任意精度的十进制类型,要么使用整数来表示分(或者一分钱的分数),并适当地显示它(就像你将日期保存为自1970年以来的秒数,但显示为dd-mm-yyyy)。

附注:永远不要使用浮点数来表示货币。真的。

附注2:永远不要使用浮点数来表示货币,绝对不要。

英文:

Never, ever, never use float for currency. Its an imprecise type, so you end up having to round calculations off and you end up losing (or gaining) a penny here and there. You may think that's ok but your accountant will be able to tell you its not. You have to be perfectly accurate.

So either use an specialised arbitrary-precision decimal type, or use an integer and hold pennies (or fractions of a penny) and display it appropriately. (just like how you hold dates as seconds since 1970, yet display them as dd-mm-yyyy).

PS. Never use floats for currency. Really.

PPS. Do not use floats for currency, not ever never ever.

答案2

得分: 2

使用表示千分之一或万分之一美分的big.Int值。浮点数在处理货币时不合适,尤其是在交易时。如果有人要求你使用浮点数,请试图说服他们这是个坏主意。如果这行不通,重新考虑与他们合作的决定。请考虑到你可能无意中涉及非法活动:https://en.wikipedia.org/wiki/Salami_slicing

回答你的技术问题:

big.Float值不像big.Int值那样具有任意精度。

SetFloat64将浮点数的精度设置为float64的精度,而SetString则根据字符串的内容进行设置。

无论哪种方式,你都应该非常谨慎地设置big.Float值的精度。你可以使用SetPrec来控制精度:https://golang.org/pkg/math/big/#Float.SetPrec

编辑:

至于注意事项,请考虑到在十进制中,0.10在二进制中没有有限的表示方式。由于big.Float没有base字段或相应的内容,它实际上无法精确存储0.10。

基本上,一旦你将浮点数作为浮点数而不是整数使用,你将失去精确值。

英文:

Use big.Int values representing 1000ths or 10000ths of a cent. Floating point values are not appropriate for money, especially when transacting. If somebody is demanding that you use floating point values, try to convince them it's a bad idea. If that doesn't work, reconsider your decision to work with them. Consider that you may inadvertently be involving yourself with something illegal: https://en.wikipedia.org/wiki/Salami_slicing

In answer to your technical question:

big.Float values are not arbitrary precision like big.Int values.

SetFloat64 sets the precision of the float to that of a float64 while SetString sets it based on what the contents of the string are.

Either way you want to be very deliberate about what precision you are setting on a big.Float value. You can control this with SetPrec https://golang.org/pkg/math/big/#Float.SetPrec

Edit:

As for pitfalls, consider that 0.10 in base 10 does not have a finite representation in base 2. Since big.Float doesn't have a base field or anything corresponding to that it can't actually store 0.10 exactly.

Basically as soon as you use the Float as a floating point number instead of as an integer you will leave exact values behind.

答案3

得分: 0

你可以使用gopkg.in/inf.v0来实现“无限精度”的十进制运算。

对于你上面指定的金额,这是一个例子:

func main() {
    var amt inf.Dec
    amt.SetUnscaled(1890)
    amt.SetScale(2)
    fmt.Println(amt.String())
    var amt1 inf.Dec
    amt1.SetUnscaled(165)
    amt1.SetScale(2)
    fmt.Println(amt1.String())
    var total = inf.NewDec(5, 2)
    total = total.Add(&amt, &amt1)
    fmt.Println(total.String())
}

它应该按预期输出20.55。

英文:

You can use gopkg.in/inf.v0 which implements "infinite-precision" decimal arithmetic.

For your specified amounts above, here's an e.g.:

func main() {
var amt inf.Dec
amt.SetUnscaled(1890)
amt.SetScale(2)
fmt.Println(amt.String())
var amt1 inf.Dec
amt1.SetUnscaled(165)
amt1.SetScale(2)
fmt.Println(amt1.String())
var total = inf.NewDec(5, 2)
total = total.Add(&amt, &amt1)
fmt.Println(total.String())
}

It should print out 20.55 as expected.

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

发表评论

匿名网友

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

确定