Skip to content
Theory · Operators · lesson 7 of 9

Floating-point numbers

Divide 1 by 3 on a calculator. The screen shows 0.3333333, even though the exact answer is an endless fraction. Not all the threes fit on the screen, so the calculator keeps an approximation.

The same happens to fractional numbers in Go. Numbers of type float64 are stored in binary, and in binary even 0.1 is an endless fraction, like a third in decimal. So the sum of two short fractions may differ from the one on paper:

GO
package main

import "fmt"

func main() {
    a := 0.1
    b := 0.2
    fmt.Println(a + b)      // 0.30000000000000004
    fmt.Println(a+b == 0.3) // false
}

Let's look at what happened, line by line:

  1. a := 0.1: memory holds the binary number closest to 0.1 instead of 0.1 itself.
  2. b := 0.2: the same goes for 0.2.
  3. fmt.Println(a + b) prints 0.30000000000000004: two tiny rounding errors added up and showed in the last digit.
  4. fmt.Println(a+b == 0.3) prints false: the sum did not match 0.3 exactly.

Here the calculator picture falls short. A calculator shows that it rounded, while Go prints the shortest form: fmt.Println(a) prints 0.1, even though memory holds an approximation. The error becomes visible only after a calculation.

This is a classic interview question: why is 0.1 + 0.2 not equal to 0.3? The answer: decimal fractions are stored in binary approximately, and the errors add up.

How a fraction is stored

Such numbers are called floating-point numbers. The way they work comes down to three facts:

  • a number keeps about 16 significant digits;
  • a separate factor sets where the point goes, so a float64 holds both 0.000001 and billions;
  • every number in it is approximate.

That is why money is not kept in a float64: cents are counted as an integer of type int.

Two fractional types

Go has two types for fractional numbers. They differ in size and precision.

float64

It takes 8 bytes and keeps about 16 significant digits. A fractional literal such as 0.1 gets this type by default, and the functions of the math package work with it.

For ordinary calculations, use float64.

float32

It takes 4 bytes and keeps about 7 significant digits. A third in it looks like 0.33333334: the eighth digit is already off.

You cannot add a float32 and a float64 without a conversion: as with integers, the operand types must match.

Constants are exact

Go adds constant literals exactly, before the program runs. So fmt.Println(0.1 + 0.2) prints 0.3, and the error appears in variables.

Comparing with a tolerance

Fractional results are not compared with ==. Instead, the check is that the difference is below a small tolerance. The math.Abs function from the math package returns the absolute value of a number:

GO
a, b := 0.1, 0.2
fmt.Println(math.Abs(a+b-0.3) < 1e-9) // true

Let's take the expression apart:

  1. a+b-0.3 is the difference between the sum and the expected answer, about 0.00000000000000006.
  2. math.Abs(...) drops the sign: it does not matter which way the sum was off.
  3. < 1e-9 compares with the tolerance. The notation 1e-9 means 0.000000001, that is, one times 10 to the power of −9.

The tolerance depends on the task. It must be larger than the rounding error but smaller than any difference that matters.

The course exercises check fractional answers with a tolerance of 1e-9. A tolerance that is too small, such as 1e-20, is below any rounding error for numbers around one and turns the comparison back into ==.

Functions of the math package

The same package has math.Sqrt, the square root. Both functions take and return a float64:

GO
fmt.Println(math.Sqrt(2))   // 1.4142135623730951
fmt.Println(math.Sqrt(16))  // 4
fmt.Println(math.Abs(-2.5)) // 2.5
  • math.Sqrt(2) prints an approximation of the square root of two: the exact value is an endless fraction.
  • math.Sqrt(16) prints 4: fmt.Println prints a whole result such as 4.0 without a point, even though the type stays float64.
  • math.Abs(-2.5) prints 2.5.

Common mistake

A variable of type int is passed to math.Sqrt:

GO
n := 16
fmt.Println(math.Sqrt(n)) // ✗
TEXT
cannot use n (variable of type int) as float64 value in argument to math.Sqrt

The program does not compile. The literal 16 adapts to the required type, but the variable n already has type int, and Go never converts it implicitly. Convert the argument explicitly: math.Sqrt(float64(n)) gives 4.

Key points

  • A float64 stores fractions approximately, so in variables 0.1 + 0.2 is not equal to 0.3.
  • Fractional results are compared with a tolerance: math.Abs(a+b-0.3) < 1e-9.
  • Money is not kept in a float64: cents are counted as an integer.
  • math.Sqrt and math.Abs take a float64, so a variable of type int is converted explicitly.

Subscribe to take the course

The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.

Sign in another way