How can we truncate float64 type to a particular precision?

Floating PointGo

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

Test Code

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:

  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

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
}

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

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionGeorge ThomasView Question on Stackoverflow
Solution 1 - Floating PointDavid CalhounView Answer on Stackoverflow
Solution 2 - Floating PointBabaView Answer on Stackoverflow
Solution 3 - Floating PointcreackView Answer on Stackoverflow
Solution 4 - Floating PointBrendenView Answer on Stackoverflow
Solution 5 - Floating Pointtrey-jonesView Answer on Stackoverflow
Solution 6 - Floating PointTobiView Answer on Stackoverflow
Solution 7 - Floating PointKarmadonView Answer on Stackoverflow
Solution 8 - Floating PointKugutsumenView Answer on Stackoverflow
Solution 9 - Floating PointmileusnaView Answer on Stackoverflow
Solution 10 - Floating PointC SouthwellView Answer on Stackoverflow
Solution 11 - Floating PointVallierView Answer on Stackoverflow
Solution 12 - Floating PointIvasanView Answer on Stackoverflow
Solution 13 - Floating PointjustinView Answer on Stackoverflow