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
Why the difference beteween using Float.SetFloat64()
and Float.SetString()
?
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.
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.
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.
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.