1

I was trying to round a number to the nearest thousand but I usually get a different result when I use math.Pow. Examples:

fmt.Println(math.Pow(10., 3.))
x := math.Pow(10, 3)
y := (((3251 - 1000) / x) * x)
fmt.Println(y)

Output: 1000
Output: 2251

GoPlayGround

When I use 1000 instead math.Pow(10., 3.) I get what I want:

y := (((3251 - 1000) / 1000) * 1000)
fmt.Println(y)

Output: 2000

Go PlayGround

What am I doing wrong? I would appreciate some help.

jub0bs
  • 60,866
  • 25
  • 183
  • 186
JMaia
  • 79
  • 4
  • 5
    The first is floating point arithmetic. This `(((3251 - 1000) / 1000) * 1000)` is integer arithmetic. – icza Jun 23 '21 at 09:39

2 Answers2

3

The expression y := ((3251 - 1000) / 1000) * 1000 is a constant expression, i.e. has only constant untyped literal operands, and it's evaluated at compile time. In particular:

If the untyped operands of a binary operation (other than a shift) are of different kinds, the result is of the operand's kind that appears later in this list

The last operands 1000 (of both the division and multiplication) are an untyped int, therefore the result of the division is also an int, and truncated to integer precision as you were expecting:

    // (3251 - 1000) -> int 2251
    // (3251 - 1000) / 1000 -> int 2
    // ((3251 - 1000) / 1000) * 1000 -> int 2000

    y := ((3251 - 1000) / 1000) * 1000
    fmt.Println(reflect.TypeOf(y)) // int

With math.Pow instead the expression is not constant anymore (it's the result of a function call), and now you have a typed float64 variable resulting from the return type of Pow:

    // (3251 - 1000) -> 2251 int
    // (3251 - 1000) / x -> 2.251 float64
    // ((3251 - 1000) / x) * x -> 2251 float64

    y := (((3251 - 1000) / x) * x)
    fmt.Println(reflect.TypeOf(y)) // float64

So in the latter case, the decimal that results from the division is preserved and you get it back after you multiply it again.

Playground: https://play.golang.org/p/v_mX3mnM6tT


To round to the nearest thousand you can use the trick suggested by @icza in this answer:

func Round(x, unit float64) float64 {
    return math.Round(x/unit) * unit
}

func main() {
    x := Round(3251-1000, 1000.)
    fmt.Println(x) // 2000
}
blackgreen
  • 34,072
  • 23
  • 111
  • 129
  • Thank you for your excellent explanation :). So if I need to round to the nearest thousand I would need to do the covnersion of math.Pow(x, y) to int, right? Or is there a better way? – JMaia Jun 23 '21 at 09:47
  • 1
    @JMaia the very short answer is that you could do it that way, but please check [this excellent answer](https://stackoverflow.com/a/39544897/4108803) by [icza](https://stackoverflow.com/users/1705598/icza) which contains a detailed explanation and guideline about rounding. – blackgreen Jun 23 '21 at 09:51
0

Based on the suggestions:

package main

import (
    "fmt"
    "math"
)

func main() {
    fmt.Println(math.Pow(10, 3))
    x := math.Pow(10, 3)
    y := (((3251 - 1000) / x) * x)
    fmt.Println(y)
    fmt.Println(Round(y, 1000))
}
func Round(x, unit float64) float64 {
    return math.Round(x/unit) * unit
}

Output:

1000
2251
2000
Gopher
  • 721
  • 3
  • 8