In the initial stage of development, we often encounter problems such as "floating point accuracy" and "money value representation".
So, how to deal with money, how to store and pass them.
Why is the problem
Standard floating point types in Go have some precision (like any other language) and you can't use them in currency operations. Here is the simplest example:
var v1, v2 = 0.1, 0.2 (v1 + v2) // Output: 0.30000000000000000004
You can calculate how many times you need to add one value to another to get extra money on your account! But the other way around is the same — in this case, you just lose your money.
This is not only problematic when doing maths on your money, but also when passing data between different systems or services.
Next question — Pass your money
Every time you marshal your money from / to float, you will encounter the same problem as above, as well as other issues related to the marshal implementation - json, xml, text, etc...
Another problem is rounding. If you are dealing with currency, you will always face rounding. How should you round your currency value? For example, 0.345 yuan, usually we will round to 0.35 yuan?
What is our choice
There are some special types that can be used for the representation and calculation of currency.
Go standard library hasType (from
math/big
package, representing a floating point number of arbitrary precision). andfloat32
andfloat64
Differently, they have fixed size and precision,Allows you to set arbitrary precision for numbers and calculations.
Another good option isdecimal
Library/shopspring/decimal
About rounding:
- 1.234 => 1.23
- 1.235 => 1.24
- 1.236 => 1.24
For example,shopspring/decimal
Provides a method to round values appropriately.
Another good option to consider is to use currency units. This way, you move from the floating point problem to the integer and calculate everything as an integer. The only place to use rounding here: pass the result value.
Now let's discuss the choices when passing currency.
- Using currency units — we pass everything as integers, there is no floating point problem here. Just control the limit of the value and it's OK.
- Pass a floating point number as a string. It's usually a good choice too - when you pass a floating point number as a string, you're safe when the other party reads this string value and converts it back to a floating point number.
Simple example
You canGo PlaygroundTry it up.
package main import ( "fmt" "/shopspring/decimal" ) func main() { a := 0.1 b := 0.2 c := (a) d := (b) (a, b, (), ()) (a + b) ((d).String()) }
The output is:
0.1 0.2 0.1 0.2
0.30000000000000004
0.3
in conclusion
When processing currency — usemath/big
Or some currency-related library, such asshopspring/decimal
, or just use currency units, don't use floating point numbers here. Pass the currency as a string, or in the currency unit, do not use floating point numbers here.
In fact, there is another trick: for these values, we can use:xxx * 10000
so that we can retain its accuracy.10000
This value can be negotiated within the team.
This is the article about how to avoid floating point accuracy problems when GO currency calculation. For more related content on floating point accuracy problems, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!