How can we truncate float64 type to a particular precision?
Floating PointGoFloating Point Problem Overview
package main
import (
"fmt"
"strconv"
)
func main() {
k := 10/3.0
i := fmt.Sprintf("%.2f", k)
f,_ := strconv.ParseFloat(i, 2)
fmt.Println(f)
}
I had to write the program above to decrease the precision of a go float64 variable to 2. In this case I was using both strconv and fmt. Is there some other logical method by which it can be done?
Floating Point Solutions
Solution 1 - Floating Point
The following code should work for a lot of simple use cases with relatively small numbers and small precision inputs. However, it may not work for some uses cases because of numbers overflowing out of the range of float64 numbers, as well as IEEE-754 rounding errors (other languages have this issue as well).
If you care about using larger numbers or need more precision, the following code may not work for your needs, and you should use a helper library (e.g. https://github.com/shopspring/decimal).
I picked up a one-liner round function from elsewhere, and also made toFixed() which depends on round():
func round(num float64) int {
return int(num + math.Copysign(0.5, num))
}
func toFixed(num float64, precision int) float64 {
output := math.Pow(10, float64(precision))
return float64(round(num * output)) / output
}
Usage:
fmt.Println(toFixed(1.2345678, 0)) // 1
fmt.Println(toFixed(1.2345678, 1)) // 1.2
fmt.Println(toFixed(1.2345678, 2)) // 1.23
fmt.Println(toFixed(1.2345678, 3)) // 1.235 (rounded up)
Solution 2 - Floating Point
You don't need any extra code ... its as simple as
import (
"fmt"
)
func main() {
k := 10 / 3.0
fmt.Printf("%.2f", k)
}
Solution 3 - Floating Point
I really don't see the point, but you could do something like this without strconv:
package main
import (
"fmt"
)
func main() {
untruncated := 10 / 3.0
truncated := float64(int(untruncated * 100)) / 100
fmt.Println(untruncated, truncated)
}
Solution 4 - Floating Point
The simplest solution is numeric truncation (assuming i
is a float and you want a precision of 2 decimal points):
float64(int(i * 100)) / 100
For example:
i := 123456.789
x := float64(int(i * 100)) / 100
// x = 123456.78
BEWARE!
If you're dealing with large numbers (numbers that can cross the max value boundaries), you should know that the above can lead to serious floating point accuracy issues:
i := float64(1<<63) // 9223372036854775808.0
fmt.Println(i, float64(int64(i * 10)) / 10)
Prints: 9.223372036854776e+18 -9.223372036854776e+17
See also:
Solution 5 - Floating Point
No one has mentioned using math/big
. The results as pertains to the original question are the same as the accepted answer, but if you are working with floats that require a degree of precision ($money$), then you should use big.Float
.
Per the original question:
package main
import (
"math/big"
"fmt"
)
func main() {
// original question
k := 10 / 3.0
fmt.Println(big.NewFloat(k).Text('f', 2))
}
Unfortunately, you can see that .Text
does not use the defined rounding mode (otherwise this answer might be more useful), but rather always seems to round toward zero:
j := 0.045
fmt.Println(big.NewFloat(j).SetMode(big.AwayFromZero).Text('f', 2)
// out -> 0.04
Nevertheless, there are certain advantages to having your float stored as a big.Float
.
Solution 6 - Floating Point
The answer by threeve brought me to this issue on GitHub where a solution based on math/big
for rounding values is presented - this way the rounding method is used correctly:
package main
import (
"fmt"
"math/big"
)
func main() {
f := new(big.Float).SetMode(big.ToNearestEven).SetFloat64(10/3.0)
// Round to 2 digits using formatting.
f.SetPrec(8)
fmt.Printf("%.2f\n", f)
}
The rounding mode is also respected in threeve's example:
j := 0.045
f := new(big.Float).SetMode(big.AwayFromZero).SetFloat64(j)
// Round to 2 digits using formatting.
f.SetPrec(8)
fmt.Printf("%.2f\n", f)
->
correctly yields 0.05
Also, Go 1.10 has been released and added a math.Round()
function, see this excellent answer by icza: https://stackoverflow.com/questions/39544571/golang-round-to-nearest-0-05/39544897#39544897
package main
import (
"fmt"
"math"
)
func main() {
fmt.Println(Round(10/3.0, 0.01))
}
func Round(x, unit float64) float64 {
return math.Round(x/unit) * unit
}
However, one should not use float
for storing monetary values. (See: https://stackoverflow.com/questions/3730019/why-not-use-double-or-float-to-represent-currency)
One way around this is using a library that implements decimal
like https://github.com/ericlagergren/decimal or https://github.com/shopspring/decimal
Solution 7 - Floating Point
Functions without checking for large floats
// Rounds like 12.3416 -> 12.35
func RoundUp(val float64, precision int) float64 {
return math.Ceil(val*(math.Pow10(precision))) / math.Pow10(precision)
}
// Rounds like 12.3496 -> 12.34
func RoundDown(val float64, precision int) float64 {
return math.Floor(val*(math.Pow10(precision))) / math.Pow10(precision)
}
// Rounds to nearest like 12.3456 -> 12.35
func Round(val float64, precision int) float64 {
return math.Round(val*(math.Pow10(precision))) / math.Pow10(precision)
}
Solution 8 - Floating Point
Be careful with longitude and latitude float as truncating and rounding can yield completely different results... as it can put you on the wrong side of a latitude boundary.
Solution 9 - Floating Point
This is a little workaround how can you round float using type conversion to int back and forth:
package main
import (
"fmt"
)
func main() {
k := 10 / 3.0
k = float64(int(k*100)) / 100
fmt.Println(k) // output 3.33
}
Solution 10 - Floating Point
func FloatPrecision(num float64, precision int) float64 {
p := math.Pow10(precision)
value := float64(int(num*p)) / p
return value
}
Solution 11 - Floating Point
So after much hunting around, coming to this thread multiple times I finally found a very simple solution that allows you to control the precision of floats very simply without any weird math!
package main
import (
"fmt"
"github.com/shopspring/decimal"
)
func main() {
k := 101.3874927181298723478
p := 5
v := decimal.NewFromFloat(k).Round(int32(p))
fmt.Println(v)
}
// 101.38749
Source: https://godoc.org/github.com/shopspring/decimal#Decimal.Round
While I like some of the simple methods like "%.3f\n", k
options, these produced a string that I would then have to convert back into a float with another strconv command that I didn't want to do.
Solution 12 - Floating Point
func truncate(val float64, precision int) float64 {
return math.Round(val * precision) / precision
}
Solution 13 - Floating Point
modify from @creack
package main
import (
"fmt"
)
func main() {
//untruncated := 10/3.0
untruncated := 4.565
tmp := int(untruncated*100)
last := int(untruncated*1000)-tmp*10
if last>=5{
tmp += 1
}
truncated := float64(tmp)/100
fmt.Println(untruncated, truncated)
}