Time precision on Linux and Windows

Unit tests were needed on a new project I’m working on and a few weeks ago I wrote some. Last week, a colleague wrote some more, and when he ran all of them, mine were failing. Same code base, up to date, same tests. On my machine they were still passing. We jumped on the situation and saw math.Floor was giving a result on my machine, which runs on Ubuntu, and another one on his, on Windows.

The code is larger, but I’ve extracted and adapted what’s needed:

package main

import (
	"time"
	"math"
)

func main() {
	datetime := time.Now().Add(time.Hour * 24 * 7 * 4 * 12 * 3)
	seconds := -1 * int(time.Now().Sub(datetime).Seconds())
	a := 29030400
	x := float64(seconds)/float64(a)

	println("input:", x, "floor:", math.Floor(x))
}

Result on Ubuntu:

input: +3.000000e+000 floor: +2.000000e+000

Result on Windows:

input: +3.000000e+000 floor: +3.000000e+000

Obviously, the good result is on Windows if you’re like me and didn’t know println does some rounding… So those are not the real values. If values are printed using fmt.Println

fmt.Println("input:", x, "floor:", math.Floor(x))

you’re going to see the real results.

Ubuntu:

input: 2.999999965553351 floor: 2

Windows:

input: 3 floor: 3

Still different values. Now the input is rounded on Windows, thus the floor value is different. The printing is now good, the problem is somewhere else, somewhere around the input.

If you look at the following two lines

datetime := time.Now().Add(time.Hour * 24 * 7 * 4 * 12 * 3)
seconds := -1 * int(time.Now().Sub(datetime).Seconds())

you can see two time.Now calls. They’re one after another, but in some cases too much time passes between them. I wouldn’t have thought of “too much time” between two internal function calls which return the time. But I didn’t know how actually time passes in computers.

Each operating system has an implementation of time, which is how the clock ticks. If you see the watch on your hand or wall ticking each second, operating systems are ticking more often. Windows ticks every 15 ms, while Linux goes down to 1 ms. This means in this case Windows is not that accurate with time, and that’s why for two consecutive time.Now calls, which are really fast, I don’t get the exactly elapsed time, the values of the two calls being different.

Run this on both Linux and Windows too see some precision differences, watch out for the monotonic value (m+=):

package main

import (
	"time"
	"fmt"
)

func main() {
	for {
		fmt.Println(time.Now().String())
	}
}

Or this:

package main

import (
	"time"
	"fmt"
)

func main() {
	for {
		fmt.Println(time.Now().Nanosecond())
	}
}

The issue can be easily fixed, by making only one call to time.Now.

package main

import (
	"time"
	"math"
	"fmt"
)

func main() {
	now := time.Now()

	datetime := now.Add(time.Hour * 24 * 7 * 4 * 12 * 3)
	seconds := -1 * int(now.Sub(datetime).Seconds())
	a := 29030400
	x := float64(seconds)/float64(a)

	fmt.Println("input:", x, "floor:", math.Floor(x))
}

With the same result on both systems:

input: 3 floor: 3

Many thanks to the guys on golang-nuts group for helping make things clear!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.