Testing in Go: testing floating point numbers
Tags: go, testing, softwareengineering, technology
Published: 2018-06-20 12:25:21

---
title: "Testing in Go: testing floating point numbers"
published: true
description: "I've been working on a library that includes some vector manipulations in Go, trying to follow good development practices and starting with writing my tests first. But early on, I ran into a bit of a problem: floating points."
tags: golang, testing, software engineering,  technology
---

*This is cross-posted from [my blog](https://juliaferraioli.com/blog/2018/06/golang-testing-floats/).*

I've been working on a library that includes some vector manipulations in Go, trying to follow good development practices and starting with writing my tests first. But early on, I ran into a bit of a problem: floating points.

# The problem

Now, we know that floating points are an issue in programming, by the very nature of how they're represented in memory. I loved Julia Evans's brief explanation of how floats work in her [Linux Comics Zine](https://jvns.ca/linux-comics-zine.pdf) (scroll down to the sixth panel), and for a more in-depth explanation, see [this piece](http://www.toves.org/books/float/#s2.1) by Carl Burch.

My problem: after writing my implementation of a vector addition function, I couldn't get the tests to pass, because comparing equality of floating point numbers is...hard. I'd be comparing an expected result of `{2,3}` to an actual result of `{1.99999999, 3.00000000002}`. Those values don't pass muster for traditional equality.

I looked up best practices of how to unit test functions that returned floating point numbers and overwhelmingly found the recommendation of "don't". (Not helpful, y'all.) One recommended using a tolerance -- that is, if the absolute difference between the expected number and the actual number was under a specified tolerance, you counted them as being equal.

That was pretty easy to implement, and it looked like this:

```go
got := Add(tt.vectors)
if math.Abs(got-tt.want) > tolerance {
    t.Fail("Got %v, expected %v", got, tt.want)
}
```

# Defining a comparer

However, I recently started to reimplement my library, and in the process switched over to testing using the cmp package. The cmp package is a much more robust way of testing for equality, and in exploring it I found that you can define a comparer function that makes this a lot cleaner. This is the comparer function from the documentation, which uses a bit more refined logic than my initial one above:

```go
const tolerance = .00001
opt  := cmp.Comparer(func(x, y float64) bool {
    diff := math.Abs(x - y)
    mean := math.Abs(x + y) / 2.0
    return (diff / mean) < tolerance
})
```

Then, when you run your test, you use the comparer function when testing for equality:

```go
if !cmp.Equal(got, tt.want, opt) {
    t.Fatalf("got %v, wanted %v", got, tt.want)
}
```

Your tests are much more clean, and you can easily use a comparer function that works for your use case.

# Postscript

However... you have to be careful with your comparer function! The one above is straight from the cmp docs. But straight away I ran into a test failure that I didn't expect: when trying to perform operations on zero vectors, it failed, *hard*. Which led to this exchange between me and my tests:

![More
Me: Tests, why are you failing?
Tests: Not telling.
Me: It's a simply equality check! Nothing complex -- I swear!
Tests: Mmmhmmm.
Me: This was my baseline test. The simplest test case I could think of.
Tests: Suuuure.
Me: Let me look. Wait...are you dividing by ZERO!?!
Tests: 😈](https://juliaferraioli.com/blog/images/golang-testing-floats/twitter-screenshot.png "Screenshot of a frustrated tweet")
Frustrated tweet, [Twitter](https://twitter.com/juliaferraioli/status/1004770471227940864)

Yep, when all the values in a vector are zero, I wind up dividing by zero. So my actual comparer function looks like this:

```go
opt  := cmp.Comparer(func(x, y float64) bool {
    diff := math.Abs(x - y)
    mean := math.Abs(x + y) / 2.0
    if math.IsNaN(diff / mean) {
        return true
    }
    return (diff / mean) < tolerance
})
```

Of course, the same logic (`diff / mean`) would have had the same issue without using the `cmp` comparer option, so this amusing story is about the general case.

But, that said, it took 8 lines of code to make my tests actually test what I care about. So if you're doing scientific computation, don't forget about your comparer function!

Go test!

*All code in this blog post is released under the [Apache 2.0 license](https://www.apache.org/licenses/LICENSE-2.0) and [can be found on GitHub](https://github.com/juliaferraioli/code_snippets/tree/master/blogs/cmp-testing/floats).*