How can we truncate float64 type to a particular precision?

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?

119937 次浏览

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)
}

You don't need any extra code ... its as simple as

import (
"fmt"
)


func main() {
k := 10 / 3.0
fmt.Printf("%.2f", k)
}

Test Code

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)
}

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)

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.

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
}

https://play.golang.org/p/yg2QYcZ-2u

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: Golang Round to Nearest 0.05

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: 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

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.

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:

  1. how 32 bit floating point numbers work
  2. how 64 bit floating point numbers work
  3. golang math numeric value range constants
  4. golang math/big
func FloatPrecision(num float64, precision int) float64 {
p := math.Pow10(precision)
value := float64(int(num*p)) / p
return value
}

因此,经过多次搜索,我终于找到了一个非常简单的解决方案,它可以让您非常简单地控制浮点数的精度,而不需要任何奇怪的数学!

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.

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)
}