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)
Answer from David Calhoun on Stack OverflowThe 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)
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
float64 precision
Because it isn't a language problem but a problem with how floats are represented in general.
https://stackoverflow.com/questions/21895756/why-are-floating-point-numbers-inaccurate
More on reddit.comWhy are my floats sometimes getting calculated and formatted differently?
The naive way (not always correct)
For truncation, we could take advantage of math.Trunc() which throws away the fraction digits. This is not exactly what we want, we want to keep some fraction digits. So in order to achieve what we want, we may first multiply the input by a power of 10 to shift the wanted fraction digits to the "integer" part, and after truncation (calling math.Trunc() which will throw away the remaining fraction digits), we can divide by the same power of 10 we multiplied in the beginning:
f2 := math.Trunc(f*1000) / 1000
Wrapping this into a function:
func truncateNaive(f float64, unit float64) float64 {
return math.Trunc(f/unit) * unit
}
Testing it:
f := 1.234567
f2 := truncateNaive(f, 0.001)
fmt.Printf("%.10f\n", f2)
Output:
1.2340000000
So far so good, but note that we perform arithmetic operations inside truncateNaive() which may result in unwanted roundings, which could alter the output of the function.
For example, if the input is 0.299999999999999988897769753748434595763683319091796875 (it's representable by a float64 value exactly, see proof), the output should be 0.2999000000, but it will be something else:
f = 0.299999999999999988897769753748434595763683319091796875
f2 = truncateNaive(f, 0.001)
fmt.Printf("%.10f\n", f2)
Output:
0.3000000000
Try these on the Go Playground.
This wrong output is probably not acceptable in most cases (except if you look at it from a way that the input is very close to 0.3–difference is less than 10-16–to which the output is 0.3...).
Using big.Float
To properly truncate all valid float64 values, the intermediate operations must be precise. To achieve that, using a single float64 is insufficient. There are ways to split the input into 2 float64 values and perform operations on them (so precision is not lost) which would be more efficient, or we could use a more convenient way, big.Float which can be of arbitrary precision.
Here's the "transcript" of the above truncateNaive() function using big.Float:
func truncate(f float64, unit float64) float64 {
bf := big.NewFloat(0).SetPrec(1000).SetFloat64(f)
bu := big.NewFloat(0).SetPrec(1000).SetFloat64(unit)
bf.Quo(bf, bu)
// Truncate:
i := big.NewInt(0)
bf.Int(i)
bf.SetInt(i)
f, _ = bf.Mul(bf, bu).Float64()
return f
}
Testing it:
f := 1.234567
f2 := truncate(f, 0.001)
fmt.Printf("%.10f\n", f2)
f = 0.299999999999999988897769753748434595763683319091796875
f2 = truncate(f, 0.001)
fmt.Printf("%.10f\n", f2)
Output is now valid (try it on the Go Playground):
1.2340000000
0.2990000000
You need to truncate decimals manually, either on string level or with math.Floor like https://play.golang.org/p/UP2gFx2iFru.
Sorry, this feels like something really simple that I'm missing. And it is something that happens in other languages, like js... but it doesn't hurt to ask.
So I have the following (I'm adding 0.01 to a variable in a loop):
https://play.golang.org/p/E_VQv8U7ha
I understand what is going on, there's a loss of precision, but how can I avoid it?
EDIT: in golang we don't have a toFixed(). I managed to fix it but I was checking if you guys know what is the best way to solve this issue.
Edit2: thank you guys for the answers. I ended up using David Calhoun's answer here: http://stackoverflow.com/questions/18390266/how-can-we-truncate-float64-type-to-a-particular-precision-in-golang
Because it isn't a language problem but a problem with how floats are represented in general.
https://stackoverflow.com/questions/21895756/why-are-floating-point-numbers-inaccurate
The best solution often depends on the context. Why do you need two points of fixed precision?
If you are dealing with currency (like USD), often the easiest solution is to convert your data to cents - 1.01 becomes 101, and you can now store it in an integer. Stripe and others do this when dealing with currency values as you only need two points of precision.
Another option is to try to determine how much precision you need. For example, if you only need 1e-9 precision you can probably just use floating points and write your code like this: https://play.golang.org/p/55sWUBov2o
This will make sure your for loop continues to run until x is at least 1e-9 greater than 1.4. This method takes a bit more practice to know when to use it, but it can be useful at times.
Hi,
I have a block of code that creates pairs of min and max values based on a given major and minor increment and a starting value. Oddly enough, certain floats are calculated differently, which doesn't make sense to me. Can someone explain why this is happening and how I can prevent it?
-10 -9.1-9 -8.1-8 -7.1-7 -6.1-6 -5.1-5 -4.1-3.9999999999999996 -3.0999999999999996-2.9999999999999996 -2.0999999999999996-1.9999999999999996 -1.0999999999999996-0.9999999999999997 -0.09999999999999964
In my example, the floats in the output after (-5, -4.1) get rendered with 16 decimal places whereas all of the previous floats only get rendered with a maximum of 1 decimal place. Also, the float variables have those decimal places (so I don't think that the fmt printf call is skewing the numbers), which is throwing off some of my downstream logic.
minValueExclNaN := float64(-10)
minValue := math.NaN()
maxValue := float64(0)
incrementMinor := float64(0.1)
incrementMajor := float64(1)
for i := 0; i < 11; i++ {
if i == 0 {
maxValue = minValueExclNaN - incrementMinor
} else {
if math.IsNaN(minValue) {
minValue = minValueExclNaN
} else {
minValue = maxValue + incrementMinor
}
maxValue += incrementMajor
// swap minValue and maxValue
if minValue > maxValue {
maxValue, minValue = minValue, maxValue
}
fmt.Printf("%v %v\n", minValue, maxValue)
}
}I have a Go Playground repro here: https://play.golang.com/p/AtlyAeMankv
Thanks!