At work today I read a piece of code in Golang that got me curious:

maxQuerySize := 500
// metricDataQueries is user provided
sliceLength := len(metricDataQueries)/maxQuerySize + 1
// what's the value of sliceLength?
for i := 0; i < sliceLength; i++ { 
    whatever
}

This is a simple integer division, but the result is then used as a parameter to the for loop. Integer division has a drawback, that can create a bug in this case: the remainder, as float in for loop declarations are supported only if no truncation happens when converting to int (i.e. 10.0 works, 10.1 don’t).

How does Go handle this? The answer is as simple as it gets: remainder is ignore and the result is the quotient. The reason why this happens is explained in Go specs, under Constant expressions:

Any other operation on untyped constants results in an untyped constant of the same kind; that is, a boolean, integer, floating-point, complex, or string constant. 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: integer, rune, floating-point, complex.

Which means that to have “true” division you need to convert at least one argument to float:

a := 10.0 / 2
b := 10 / 2.0
c := 10.0 / 2.0
d := 10 / 2
fmt.Println(reflect.TypeOf(a)) // float
fmt.Println(reflect.TypeOf(b)) // float
fmt.Println(reflect.TypeOf(c)) // float
fmt.Println(reflect.TypeOf(d)) // int

go.dev/play

Doing integer division effectively “floors” the result of the operation. What if we want the ceil of it?

Go math package has Ceil(x float64) float64 for this, which implies doing a “true” division and applying Ceil on the result.

All of this was not really surprising, but it’s cool to see this being implemented in a very ergonomic way.

Just a note: if you want to get a float value you must convert to float all division arguments before the division.

Do this: float64(2) / float64(10) // 0.2 and not this: a := float64(2 / 10) // 0.