Floats in Go

Shiraz Khan's Avatar

Shiraz Khan

LinkedIn

Senior Software Engineer

27 January 2025 / 5 min read

We already had a look at integers, now let’s dive deep into floats in Go (Golang). We’ll cover everything from basic concepts to advanced details, including representation, precision, operations, and best practices.

What are Floats in Go?

Floats in Go are data types used to represent floating-point numbers, which are numbers with a fractional component. Go provides two floating-point types:

By default, floating-point literals in Go are of type float64.

Floating-Point Representation

Floats in Go follow the IEEE 754 standard for floating-point arithmetic. This standard defines how floating-point numbers are represented in binary.

Propertyfloat32float64
Size32 bits (4 bytes)64 bits (8 bytes)
Sign Bits11
Exponent Bits811
Mantissa (Fraction) Bits2352
Precision~7 decimal digits~15 decimal digits
Range±1.18e-38 to ±3.4e38±2.23e-308 to ±1.8e308
Default UseNoYes (default for floating-point literals)
Memory UsageLowerHigher
PerformanceFaster on some 32-bit systemsGenerally as fast as float32 on modern CPUs
Use CaseMemory-constrained environmentsGeneral-purpose, higher precision needs (still not 100% precise due to the nature of floats)

Declaring and Initializing Floats

var f1 float32 = 3.14
var f2 float64 = 3.141592653589793
f3 := 3.14 // Type inferred as float64

Floating-Point Literals

Floating-point literals can be written in decimal or scientific notation:

a := 3.14        // Decimal notation
b := 6.022e23    // Scientific notation
c := 1.616e-34   // Small numbers

Zero Value

The zero value for floating-point types (float32 and float64) is 0 (zero). This is the default value assigned to a variable of type float32 or float64 if it is declared but not explicitly initialized.

var f32 float32
var f64 float64

fmt.Println(f32) // Output: 0
fmt.Println(f64) // Output: 0

Precision and Rounding Errors

Floating-point numbers are approximations due to their binary representation. This can lead to rounding errors:

fmt.Println(0.1 + 0.2) // Output: 0.30000000000000004

Why?

Special Values

Floats in Go can represent special values defined by IEEE 754:

Checking for Special Values

import "math"

x := math.Inf(1)  // Positive infinity
y := math.Inf(-1) // Negative infinity
z := math.NaN()   // NaN

fmt.Println(math.IsInf(x, 1))  // true
fmt.Println(math.IsInf(y, -1)) // true
fmt.Println(math.IsNaN(z))     // true

Arithmetic Operations

Go supports standard arithmetic operations on floats:

a := 3.14
b := 2.71

sum := a + b
diff := a - b
prod := a * b
quot := a / b

Comparison of Floats

Due to precision issues, direct comparison of floats using == is not recommended. Instead, use a small tolerance (epsilon) to compare floats:

import "math"

const epsilon = 1e-9

func almostEqual(a, b float64) bool {
    return math.Abs(a - b) < epsilon
}

fmt.Println(almostEqual(0.1+0.2, 0.3)) // true

Type Conversion

Go requires explicit type conversion between float32 and float64:

var f32 float32 = 3.14
var f64 float64 = float64(f32)

Mathematical Functions

The math package provides many functions for working with floats:

import "math"

x := 2.0
y := math.Sqrt(x) // Square root
z := math.Pow(x, 3) // x^3
a := math.Sin(x)  // Sine of x
b := math.Log(x)  // Natural logarithm

Formatting Floats

Use fmt package to format floats:

f := 3.141592653589793
fmt.Printf("%.2f\n", f) // 3.14
fmt.Printf("%e\n", f)   // 3.141593e+00 (scientific notation)
fmt.Printf("%g\n", f)   // 3.141592653589793 (compact representation)

Parsing Floats from Strings

Use strconv.ParseFloat to parse strings into floats:

s := "3.14"
f, err := strconv.ParseFloat(s, 64) // 64-bit float
if err != nil {
    fmt.Println("Error:", err)
}
fmt.Println(f) // 3.14

Best Practices

Common Pitfalls

Avoid floats when precision matters

As already discussed, if you absolutely have to use floats, then use float64 in most cases, except in memory-constrained environment.

In many cases though the question is not whether to use float64 or float32, but whether to use floats at all.

Let’s have a common scenario in real-world applications where floats intuitively seem as the first choice: Money.

Floating-point types are not suitable for representing monetary values due to their inherent imprecision. Here’s why:

Precision Issues

Floating-point numbers are approximations. For example, 0.1 cannot be represented exactly in binary, leading to small rounding errors:

fmt.Println(0.1 + 0.2) // Output: 0.30000000000000004

This imprecision can accumulate over multiple calculations, leading to significant errors in financial computations.

Rounding Errors

Financial calculations often require exact decimal precision (e.g., cents in dollars). Floats cannot guarantee this precision:

balance := 100.0
interest := 0.01
for i := 0; i < 365; i++ {
    balance += balance * interest
}
fmt.Println(balance) // May not match exact expected value due to rounding errors

Better Alternative: Use Integers

It’s better to represent monetary values as integers (e.g., cents instead of dollars) and perform all calculations using integers to avoid floating-point inaccuracies.

We can still convert to floats only for display purposes at the end when all calculations have been done.

Example:

// Represent $100.50 as 10050 cents
balance := 10050 // In cents
interest := 10   // 0.10% as an integer

// Calculate interest
balance += (balance * interest) / 100

// Convert to dollars for display
fmt.Printf("Balance: $%.2f\n", float64(balance)/100)

Advantages of Using Integers:

When to Use Floats

Use floats only when precision is not critical, because floats are always approximations.